Оцените пожалуйста "ООП код"

Ulen

Новичок
В общем то я задавал вопрос в другой теме и решил попробовать исправить свой код.
То, что было ранее, можно увидеть по ссылке выше. Вот то, что получилось сейчас:
PHP:
class UserInfo {

    public $uid;

    function get_info() {

        $uid = $this -> uid;

        $db = new PDO('sqlite:info_data.db');

        $value = $db -> prepare('SELECT * FROM `user_info` WHERE `user_id` = :uid');
        $value -> bindParam(':uid', $uid, PDO::PARAM_INT);
        $value -> execute();
        $result = $value -> fetch(PDO::FETCH_ASSOC);

        return $result;
    }
}

class html extends UserInfo {

    public $user_id;
    public $div_class_name;

    function show_info() {

        $user_id = $this -> user_id;
        $div_class_name = $this -> div_class_name;

        $this -> uid = $user_id;
        $user_info = $this -> get_info();

        echo ('<div class="'.$div_class_name.'"><span class="text_bold">'.$user_info['name'].'</span></div>');
    }
}
Все таки тяжело отделаться от процедурного подхода.

ИМХО не все гладко у меня, как считаете?
 

fixxxer

К.О.
Партнер клуба
Как и было - процедуры, обернутые в классы.

Методом тыка тут не научишься, читай литературу*.

* Посоветуйте ему кто-нибудь, я не знаю подходящих книг, легких для "вхождения" в тему
 

Yuriy_S

-=PHP-Club=-
Книги тут только на 50% смогут помочь.
Для того, что бы начать писать хороший ООП код - нужно начать мыслить объектно, тогда прийдет понимание и опыт. Ну а дальше, уже имея базовые навыки и представления - можно смотреть в сторону применения паттернов.
Начинать изучение вопроса сразу со сложной литературы и применения паттернов, ИМХО, не стоит, это собьет тебя с толку. Применять паттерны и подобные фишки нужно тогда, когда прийдет понимание, что это нужно.
А книжки - любые для начинающих в ООП, известных авторов. И не одну, а лучше несколько прочитать, да бы взглянуть с разных сторон.
 

WMix

герр M:)ller
Партнер клуба
выкинь html, начни с самого простого class UserInfoRepo->getById(42) хочу на id получить не массив, а обьект, чтоб увидеть смысл, пусть у этого обьекта будет некий метод getAge() и getBirthdate( $format ).
 

С.

Продвинутый новичок
Предлагая тебе переименовать классы в существительные, полагал, что тебя это натолкнет на некоторые мысли. getUserInfo стал userInfo. Не фонтан, ну ладно, для примера сойдет. Во что должен превратиться showUserInfo? ์Ну по всей логике тоже в userInfo. Почему такой конфликт получается? Да потому что там как минимум напрашивается один общий класс, который ты зачем-то упорно делишь на два.
 

Ulen

Новичок
выкинь html, начни с самого простого class UserInfoRepo->getById(42) хочу на id получить не массив, а обьект, чтоб увидеть смысл, пусть у этого обьекта будет некий метод getAge() и getBirthdate( $format ).
Т.е если я правильно понял:
Мне нужно сделать один класс (UserInfo). У него будет не один метод, который сейчас возвращает массив. а несколько (методов). Каждый метод возвращает нужное значение - getAge() возраст, getBirthdate() дату рождения и т.д
Правильно?

Во что должен превратиться showUserInfo? ์Ну по всей логике тоже в userInfo. Почему такой конфликт получается? Да потому что там как минимум напрашивается один общий класс, который ты зачем-то упорно делишь на два.
Я предполагаю, что класс UserInfo возвращает только данные. Далее класс showUserInfo его наследует и возвращает мне все это дело в "оформленном" виде.
Изначально у меня было все в одном классе (UserInfo), где было 2 метода - get_info() и show_info().

А потом я подумал, а что если мне нужно будет получить какие то данные снова, уже в другом классе. Писать снова метод, который будет аналогичный get_info()? Поэтому появилось 2 класса. Первый (UserInfo) я смогу унаследовать в другом месте (например третьем классе) и получить все что нужно.
 

С.

Продвинутый новичок
А потом я подумал, а что если мне нужно будет получить какие то данные снова, уже в другом классе. Писать снова метод, который будет аналогичный get_info()?
С какой это стати ты будешь добывать эти данные о пользователе в другом классе? Для получения их и введен твой userInfo. А если тебе где-то надо в третьем классе данные пользователя, то и используй этот класс userInfo, а не заново крапай тот-же код. И никакое наследование тут не нужно.
 

Ulen

Новичок
С какой это стати ты будешь добывать эти данные о пользователе в другом классе? Для получения их и введен твой userInfo. А если тебе где-то надо в третьем классе данные пользователя, то и используй этот класс userInfo, а не заново крапай тот-же код. И никакое наследование тут не нужно.
Приведу пример: в одном случае мы достаем данные и оборачиваем в html код (далее выводим и т.д). В другом случае нам например нужно просто вытащить данные (не оборачивая) и что либо с ними сделать (проверить/перезаписать и т.д).

В первом случае мы обращаемся к классу B, который в свою очередь обращается к классу A, берет данные из него и делает с ними все что нужно.
Во втором случае мы обращаемся к классу C, который также обращается к A и делает с данными что-то другое (отличное от того, что делается в классе B).

Без наследования же нельзя обратиться к методу класса A из класса B. Разве не так?
Читал об этом тут (пример 2, в классе B вызывают метод foo() из класса A).

На разных форумах видел примеры, где прямо в методе создают экземпляр другого класса и работают с ним. Это правильно?

upd: Можно же в классе B создать конструктор, который и будет создавать экземпляр класса A. Тогда и наследование не нужно.
Единственный минус, при обращении к классу B, будет постоянно дергаться класс A и соответственно соединение с базой.
 
Последнее редактирование:

Фанат

oncle terrible
Команда форума
Если бы люди размножались как у тебя классы, то твоим наследником был бы тостер.
Ну не может класс-возвращатель данных быть предком класса-отображателя. У них ВСЕ разное.
 

С.

Продвинутый новичок
upd: Можно же в классе B создать конструктор, который и будет создавать экземпляр класса A. Тогда и наследование не нужно.
Единственный минус, при обращении к классу B, будет постоянно дергаться класс A и соответственно соединение с базой.
Как-будто с наследованием оно не будет дергаться. От постоянных дерганий зщищаются совершенно другими методами, наследование тут никаким боком не поможет.
 

whirlwind

TDD infected, paranoid
Приведу пример: в одном случае мы достаем данные и оборачиваем в html код (далее выводим и т.д). В другом случае нам например нужно просто вытащить данные (не оборачивая) и что либо с ними сделать (проверить/перезаписать и т.д).

В первом случае мы обращаемся к классу B, который в свою очередь обращается к классу A, берет данные из него и делает с ними все что нужно.
Теперь напиши то же самое только по отношению к числу. Представь, что число это объект целочисленного типа. Класс - это новый тип. Объект - это экземпляр нового типа. Что ты будешь делать, что бы сформировать некий html код для отображения целочисленного значения (то есть объекта класса число). Ты будешь чего то там наследовать? Но ведь нет. Ты просто напишешь функцию типа integerToHtml и передашь ей объект класса число в качестве аргумента. Все абсолютно то же самое с объектами других классов. Пока ты думаешь, что для выполнения какой либо опереции над объектом тебе нужно расширить класс, ты думаешь в процедурном стиле. Как только ты начинаешь передавать объекты между обектами других классов, ты начинаешь думать в объектом стиле. Создать новый тип, то есть сгруппировать некоторые данные в отдельный класс, это упростить понимание системы на уровне абстракции. В случае с пользователем, это атрибуты пользователя, такие как имя, возраст и тп. Но способ представления информации о пользователе не является информацией о пользователе. А ты пытаешься запихать все в одно. Ты пытаешься просто решить задачу любым способом, а не скомпоновать код таким образом, что бы упростить его понимание на уровне абстрактных образов (база данных, пользователь, вид, html документ, etc...).

Или возьми для примера и сделай обертку над целочисленным значением. У тебя появится новый тип class Integer. У него будет один атрибут - целочисленное значение value. А методы этого класса будут представлять операции над этим значением. Например, операция (метод) сложения данного объекта с другим объектом класса Integer. Что получаем в итоге. Во первых, мы можем работать с объектами как с простыми переменными не заморачиваясь внутренней сложностью объектов этого типа. Мы можем их (объекты, не классы) передавать в функции и методы других классов. Но когда нужно, мы можем обратиться к деталям объекта. Класс это всего лишь шаблон, которому соответствует объект. Когда ты говоришь обращается к классу, это значит он обращается к шаблону. Шаблон это просто функция. Отсюда и вывод, что когда ты мыслишь на уровне классов ты мыслишь в процедурном стиле. Что бы мыслить в объектом стиле ты должен воспринимать объекты. Объект это совокупность неких данных и методов их обработки. Например, целое число и метод сложения с другим целым числом. Такое объединение данных и функционала называется инкапсуляцией.
 

Ulen

Новичок
Как-будто с наследованием оно не будет дергаться. От постоянных дерганий зщищаются совершенно другими методами, наследование тут никаким боком не поможет.
Да, я поразмыслил, одинаково получается в обоих случаях.

Или возьми для примера и сделай обертку над целочисленным значением.
Спасибо за объяснение, написано так, что невозможно не понять. Некоторые моменты стали проясняться.
Как вы и сказали, нужно менять мышление с процедурного на объектное. Думаю книги и собственный настрой должны помочь с этим.

Я попробовал применить ваш пример с числом на своем примере в шапке темы.

То, как я бы писал с процедурным подходом:
PHP:
class Integer {

    public $value_one = 5;

    function int_value() {
        return $this -> value_one;
    }
}

class Sum extends Integer {

    public $value_two;

    function sum_int() {
        $value_two = $this -> value_two;
        $value_one = $this -> int_value();
        $sum_value = $value_two + $value_one;
        print $sum_value;
    }
}
И то, как вы предложили с объектным:
PHP:
class Integer {

    public $value_one = 5;
    public $value_two;

    function int_sum() {
        $number_one = $this -> value_one;
        $number_two = $this -> value_two;
        $sum = $number_one + $number_two;
        print $sum;
    }

    function int_diff() {
        $number_one = $this -> value_one;
        $number_two = $this -> value_two;
        $diff = $number_one - $number_two;
        print $diff;
    }
    ...
}
 
Последнее редактирование:

Sufir

Я не волшебник, я только учусь
А теперь, тебе не кажется странным, что "число" само над собой операции выполняет?
И ещё подсказка: функции и методы могут принимать аргументы, ты никак этим не хочешь пользоваться почему-то...
 
Последнее редактирование:

whirlwind

TDD infected, paranoid
Первый вариант был объектно-ориентированный. Просто немного иначе нужно:

PHP:
class Integer {

  public $value;

  public __construct($value) {
    $this->value = $value;
  }

  // int нам уже не нужно. мы и так знаем что это int.
  // и ничего другого в этом классе по хорошему быть не должно.
  // это называется single responsibility
  // - принцип единичной (единственной, выделенной) ответственности
  function value() {
    return $this->value;
  }

  function sum(Integer $other) {
    return new Integer($this->value + $other->value);
  }
}
Второй вариант представляет уже не Integer, а пару Integer, то есть это класс IntegerPair. Ведь он инкапсулирует два целочисленных значения.
 

fixxxer

К.О.
Партнер клуба
@Ulen, я понял твою основную ошибку. Ты почему-то зациклился на наследовании. Наследование вообще не имеет прямого отношения к ООП, это необязательная штука. Смысл ООП прежде всего в полиморфизме. Это напрямую завязано на понятие интерфейса. Интерфейс сообщает нам, что "эта штука" умеет делать, при этом нас снаружи не волнует, как именно.

На примере, наверное, будет понятнее. Допустим, у нас есть простое приложение - хранилище файлов. Мы хотим уметь сохранить загруженный пользователем файл, получить на него ссылку, и удалить файл, итого нам нужен интерфейс, скажем, такой, если немного все упростить:

PHP:
interface FileStorageInterface {

     /**
      * @param string $file_local_path
      * @return int file_id
      */
     public function put($file_local_path);

     /**
      * @param int $file_id
      * @return string
      */
     public function getUrl($file_id);

     /**
      * @param int $file_id
      * @return void
      */
     public function delete($file_id);
}
Мы решили хранить файлы локально, окей, пишем реализацию - class LocalFileStorage implements FileStorageInterface. В конфиге DI указываем, что FileStorageInterface реализуется LocalFileStorage - и это единственное место, где мы ссылаемся на конкретную реализацию LocalFileStorage, во всем остальном коде мы работаем с FileStorageInterface.

Теперь мы заняли файлами первый, второй, третий сервер, и решили, что хотим не возиться со своим железом, а хранить файлы на Amazon S3. Отлично, пишем class AmazonS3FileStorage implements FileStorageInterface, меняем в конфиге DI LocalFileStorage на AmazonS3FileStorage - и всё, больше ничего менять не надо. Весь "внеший" для этой задачи код просто работает с интерфейсом, для него замена реализации не имеет значения. Это как бы и есть полиморфизм.

Да, в контракте FileStorageInterface :: put() мы принимаем путь к файлу, а возвращаем id. Наверное, когда мы писали LocalFileStorage, нам для реализации аллокации идентификатора понадобится сделать запись в базе данных, например, и еще в той же базе данных мы храним номер сервера, по которому строится URL к файлу в getUrl(). Отлично, принимаем там в конструкторе FileRepositoryInterface и так далее. Зависимости передаем везде через конструкторы, указывая там исключительно интерфейсы, а все конкретные реализации прописываем в конфиге DI - и так далее, и так везде.

А наследование нам тут вообще не понадобилось. :)

Можно заметить, что контракт метода put() несколько неудачный - мы возложили аллокацию file_id на FileStorageInterface, которому до этого дела в общем-то нет. Подумай, как улучшить. ;)
 
Последнее редактирование:
Сверху