Автор Гілка: ООП (складно, тому що ДужеБагатоВаріантів)  (Прочитано 11988 раз)

gdekjifgb

  • Гість
Майже рік назад я зрозумів ООП. І почав переписувати код. Але виникають "проблеми". Проблеми виникають тому що при/для реалізації коду на ООП існує дуже багато варіантів написання.

Буває так що сьогодні написав один код, а вночі (або наступного дня) придумалась краща реалізація. І знову той самий код доводиться переписувати. І така оказія буває частенько. Тому припинив переписування.

Через це я майже рік не можу підступитись до свого фрінкенштейну.

"Проблем" не описую, бо мало хто з вас буде вникати в суть написаного. Але якщо потрібно, можу словами написати в чому проблеми.

Було б чудово дізнатися ваш досвід у ООП. Впевнений що у вас уже є свій підхід до цього.

Книги по ООП у мене є, відео повно - я їх регулярно читав, думав-думав, і нарешті до мене дійшло ООП.

Цікава штука!: Еволюція мислення відбулась миттєво (мене ніби переклинило!), але для усвідомлення цього моменту мені знадобилось три доби.

Мабуть доведеться описувати проблематику? Можливо ви скажете що тільки власний досвід допоможе? Ну так я хочу дізнатись як і що у інших.
« Змінено: 2019-09-10 12:52:02 від gdekjifgb »

Відсутній ysenko

  • Новачок
  • *
  • дописів: 35
  • Карма: +1/-0
  • Python developer
Доброго дня,

Якщо бажаєте одержати більш конкретну відповідь, то краще, таки, описувати проблеми з якими зіткнулися. Загалалом є кілька практик/підходів, але основне, як на мене це:
  • Правильно обирати абстракції - відповідно до домену задачі
  • Не нагромаджувати додаткових і непотрібних абстракцій KISS
  • Намагатися дотримуватися SOLID
Загалом, практика вирішує. Допомагає попрацювати в команді з більш досвідченими людьми які буду рев'ювати ваш код.
« Змінено: 2019-09-14 10:52:26 від ysenko »
import antigravity

gdekjifgb

  • Гість
Ніхто мого коду ніколи не бачив. Спасибі за посилання. Дещо я вже знаю (от саме тому і зупинився на початку переписування коду), дещо потрібно прочитати...

Пізніше буде опис проблем. Буду намагатись не писати зайвого щоб нікого не заплутувати.

Відсутній lpi3

  • Новачок
  • *
  • дописів: 30
  • Карма: +0/-0
теж довго не міг „зрозуміти“ ооп. в більшості писав на с++ і було якесь постійне відчуття незадоволення. здавалося, що с++ якийсь неповноцінний. і із-за того читав про інші мови програмування намагаючись їх використовувати у роботі.багато працював з erlang, але з динамічною типізацією важко робити великі проєкти.
змінив свою точку зору на с++ (і відповідно java c# та інші ооп мови) після того як добре вчитався у патерни проєктування.

gdekjifgb

  • Гість
Піймав я колись "глюк" пов'язаний з динамічною типизацією: в case були числа  і текстові рядки  :laugh: . Не відразу зрозумів що і до чого.

Описую "проблему":
- користувач може створювати Пост (текстове повідомлення), може його редагувати, і тд., але тут розглядаємо тільки створення і редагування Поста.

- Пост має два параметри доступу. 1 - доступ до перегляду, 2 доступ до коментування.

- при відправці Поста на сервер в бд, перевіряються параметри доступу (допустимі значення 1, 2, 3)

Якби в проекті був тільки Пост, можна було б  написати приватний метод і все, але в  проекті іще є фотоальбоми користувача.

Якби були тільки Фотоальбом і Пост можна було б створити клас в якому написати загальні методи для Фотоальбому та Поста і зробити наслідування від/з цього класу. Тобто загальні методи (в даному  прикладі ПеревіркаПараметрівДоступу) "приклеїлись" б до класу Пост та класу Фотоальбом.

- коли з'являться нові приблуди (голосування, музика, відео та інш) на сайті, то можна буде за допомогою наслідування вирішити проблему перевірки параметрів доступу, але я вважаю що таке наслідування це вже ускладнення.

Тому в даному випадку бачу таке правильне рішення:
- створити клас ПараметриДоступу і при необхідності звертатись до методів об'єкта створеного на його основі.


Як у мене зараз? (коли я почав переписування, потім зупинився): Є клас Checker і в ньому багато всяких перевірок; і параметри доступу,і довжина текстового рядка, і багато іншого.

Чи правильно буде те рішення, що я виділив жирним?

« Змінено: 2019-09-16 18:54:36 від gdekjifgb »

Відсутній lpi3

  • Новачок
  • *
  • дописів: 30
  • Карма: +0/-0
Тому в даному випадку бачу таке правильне рішення:
- створити клас ПараметриДоступу і при необхідності звертатись до методів об'єкта створеного на його основі.

Як у мене зараз? (коли я почав переписування, потім зупинився): Є клас Checker і в ньому багато всяких перевірок; і параметри доступу,і довжина текстового рядка, і багато іншого.

Чи правильно буде те рішення, що я виділив жирним?
я зробив би так: пост, фотоальбом та інші, мають реалізовувати інтерфейс checkerProvider, через який можна отримати доступ до інтерфейсу checker, з якого можна отримати права доступу. Так що нібито все вірно. Хоча я і не зрозумів, чи питання в контексті с++ чи якогось динамічного (js наприклад).
struct iChecker;

struct iCheckerProvider {
  virtual ~iCheckerProvider() {}
  virtual iChecker &getChecker() = 0;
};

struct iChecker {
  virtual ~iChecker() {}
  virtual bool canRead() = 0;
  virtual bool canWrite() = 0;
  virtual bool canWhatever() = 0;
};

struct iData; // data to store or modify

struct iElement : iCheckerProvider {
  virtual ~iElement() {}
  virtual iData &getData() = 0;
}
struct Post : iElement { .. impl};struct Gallery : iElement {.. impl};struct Music : iElement {..impl};
void writeToBD(iElement &e) {
  if (e.getChecker().canWrite()) {
    iData &d = e.getData();
    database->write(e);
  } else {
    throw PermissionError();
  }
}

на всяк випадок, наслідування від вже існуючих класів вважається  bad practice. краще написати купу інтерфейсів і наслідуватися від них один раз. а далі комбінуючи ці реалізації інтерфейсів створювати групу об'єктів, які вирішують задану проблему. конкретно в с++ в багатьох випадках замість наслідування інтерфейсів краще використовувати шаблони, бо так: 1 - простіше (не треба визначати інтерфейси явно, компілятор сам побудує список методів які мають буди у об'єкті); 2 - швидше, бо не буде витрат на перехід по vtable.

Відсутній ysenko

  • Новачок
  • *
  • дописів: 35
  • Карма: +1/-0
  • Python developer
Піймав я колись "глюк" пов'язаний з динамічною типизацією: в case були числа  і текстові рядки  :laugh: . Не відразу зрозумів що і до чого.
Мабуть, таки не з динамічно типізованими а зі слаботипізованимим мовами, які дозволяють неявні приведення типів. Python, наприклад, не дозволить додати рядок до числа, якщо явно не привести їх до одного типу. Динамічна типізація означає тільки те, що тип змінної або виразу буде відомий тільки під час роботи програми.

Описую "проблему":
- користувач може створювати Пост (текстове повідомлення), може його редагувати, і тд., але тут розглядаємо тільки створення і редагування Поста.

- Пост має два параметри доступу. 1 - доступ до перегляду, 2 доступ до коментування.

- при відправці Поста на сервер в бд, перевіряються параметри доступу (допустимі значення 1, 2, 3)

Якби в проекті був тільки Пост, можна було б  написати приватний метод і все, але в  проекті іще є фотоальбоми користувача.

Якби були тільки Фотоальбом і Пост можна було б створити клас в якому написати загальні методи для Фотоальбому та Поста і зробити наслідування від/з цього класу. Тобто загальні методи (в даному  прикладі ПеревіркаПараметрівДоступу) "приклеїлись" б до класу Пост та класу Фотоальбом.

- коли з'являться нові приблуди (голосування, музика, відео та інш) на сайті, то можна буде за допомогою наслідування вирішити проблему перевірки параметрів доступу, але я вважаю що таке наслідування це вже ускладнення.

Все описане вижче суперечить принципу "single responsibility" SOLID. Ваші абстракції, які відповідають за пост, фотоальбом і т.д. не повинні віиконувати ніяких додаткових функцій, що суперечать їх первинній природі.

Тому в даному випадку бачу таке правильне рішення:
- створити клас ПараметриДоступу і при необхідності звертатись до методів об'єкта створеного на його основі.

Як у мене зараз? (коли я почав переписування, потім зупинився): Є клас Checker і в ньому багато всяких перевірок; і параметри доступу,і довжина текстового рядка, і багато іншого.

Чи правильно буде те рішення, що я виділив жирним?

Що робити? Одне з можливих рішень - додати ще одну абстракцію, єдиною метою якої буде авторизація дії користувача. Тобто вона має дати однозначну відповідь на запитання - чи може конкретний користувач виконати певну дію (наприклад побачити, створити, змінити, видалити) над певним об'єктом системи (наприклад пост, відео, фото і т.д.).
« Змінено: 2019-09-18 15:00:33 від ysenko »
import antigravity

gdekjifgb

  • Гість
Хоча я і не зрозумів, чи питання в контексті с++ чи якогось динамічного (js наприклад).
php


наслідування від вже існуючих класів вважається  bad practice. краще написати купу інтерфейсів і наслідуватися від них од
Я також думаю що з наслідуванням можна легко заплутатись.
Що робити? Одне з можливих рішень - додати ще одну абстракцію, єдиною метою якої буде авторизація дії користувача. Тобто вона має дати однозначну відповідь на запитання - чи може конкретний користувач виконати певну дію (наприклад побачити, створити, змінити, видалити) над певним об'єктом системи (наприклад пост, відео, фото і т.д.).
Тому в даному випадку бачу таке правильне рішення:
- створити клас ПараметриДоступу і при необхідності звертатись до методів об'єкта створеного на його основі.
Суттєва підказка, тому що іще є перевірка авторства об'єкта (автора поста, фотоальбому, ліміту часу на редагування коментаря не під своїм постом, картинкою...).


Гляну у минулорічний код і щось напищу, допишу, перепишу і опублікую тут для критики та обговорення. Але я більш схиляюсь до створення окремого класу.
« Змінено: 2019-09-18 16:46:16 від gdekjifgb »

gdekjifgb

  • Гість
написав, перевірив (провів тести): все чудово працює, але я не дуже задоволений. В цьому і проблема :( - сьогодні так, завтра по іншому.

Спочатку думав створити окремий клас що матиме відношення тільки до ПараметрівДоступу (наукові методи радять все і вся розділяти на найменші складові).

Потім вирішив що потрібно зробити (ввести) абстракцію що буде відповідати (перевіряти) авторизованість дій користувача: Чи може користувач зробити те чи інше, але якщо таке зробити то в цю абстракцію багато зайвого ввійде. Получиться так як з перевірками у немаленькому класі Cheсker.
Тому вирішив у класах Пост, Фото, Фотоальбом реалізувати методи
interface I_Post{
    public function get_view_acc();
    public function get_comm_acc();
    public function is_avtor();
//    ...
//    ...
}

interface I_Foto{
    public function get_view_acc();
    public function get_comm_acc();
    public function is_avtor();
//    ...
//    ...
}
class Post implements I_Post {

    public function __construct($ddb, $pid) {
        //Запит до бд і заповнення класу даними
        $q = 'select uid_posts,postacc,postcomacc from posts where pid=?';
        $rawdata = $ddb->prepare($q);

        $qr = new qresult($ddb);

......

       $x = $qr->numrow($rawdata);
        if ($x != 1) {
            //нема поста
            $rawdata->close();
//            er(51);
            $e = new err();
            $e->er(51);          //НЕМА ПОСТА і exit
        }

        $res = $rawdata->bind_result($this->uid_posts, $this->view_acc, $this->comm_acc);
        $qr->bindres($res);
        $res = $rawdata->fetch();
        $qr->fetch($res);
        $rawdata->close();
    }

    public function get_view_acc() {
        return($this->view_acc);
    }

    public function get_comm_acc() {
        return($this->comm_acc);
    }

    public function is_avtor() {
        //без перевірки залогыненості
        if ($_SESSION['uid'] == $this->uid_posts) {
            //uid_posts - автор поста
            return(true);
        }
        return(FALSE);
    }

}
Ніяких write set save по відношенню до ПараметрівДоступу нема, тому що ці параметри записуються в  бд одним запитом (insert), і змінюються також одним запитом до бд (update).

Надходять ПареметриДоступу від користувача тільки з масиву $_POST. Він у мене перевіряється на коректність.
Стосовно назв класів: тут не все просто.
Назви класів Post, Foto ... - не мають конкретики і мені не підходять, тому що:

- користувач може створити НовийПост, може  редагувати ІснуючийПост. Так же само і з фотоальбомами та окремими картинками (а також з коментарями).

- при створенні нового - обробка буде одна,
- при редагування ІСНУЮЧОГО обробка буде інша.

Конкретніше: При створенні нового об'єкта*: перевіряється залогіненість користувача, перевіряються параметри об'єкта (якщо це пост або коментар то перевіряється довжина текстових рядків, якщо завантажується картинка: перевіряєься розмір (мб), формат файлу, розмір картинки (сторони у пікселях)).

При редагуванні спочатку перевіряється залогіненість користувача, потім НАЯВНІСТЬ ОБ'ЄКТА, потім авторствр об'єкта, потім конвертація тегів для виводу у форму для редагування, та інш.

Я вважаю що потрібно створювати окремі класи для НовогоПоста (NewPost), для РедагуємогоПоста (EditedPost)... і для всих інших об'єктів що створюються або редагуються.


Так правильно чи існують інші реалізації?

---
* написання, відправка поста, створення фотоальбом, завантаження картинки. Слово "об'єкт" в даному випадку це не те що створюється оператором new
« Змінено: 2019-09-18 23:10:49 від gdekjifgb »

gdekjifgb

  • Гість
Все-таки складно навчитись думати на ООП. Практика і досвід  :'( допоможуть найкраще.

gdekjifgb

  • Гість
Не пройшло 1 години, а у мене вже інші думки з'явились.
А що якщо написати код(клас?) у який буде передаватись $obj (об'єкт)  і по ньому будуть перевірятись (тобто братись) ПараметриДоступу? Це ж буде  100% поліморфізм(!) Тільки навіщо писати код заради поліморфізму?
---
зараз накодую і перевірю як воно буде...
Провів тестування, працює. І якщо так як нижче, то виходить менше коду!, але я не знаю що буде якщо далі продовжити кодування(?), бо це тільки приклад ЯК я думаю і ЯК намагаюсь писати. Бо до цього я в тому році не дійшов.

Ось приклад з 100% поліморфізмом (якщо я по дурості нічого не плутаю):
interface I_UserAvtorize{
    public function get_view_acc($obj);
    public function get_comm_acc($obj);
    public function is_avtor($obj);
}
class UserAvtorize implements I_UserAvtorize {

    public function get_view_acc($obj) {
        return($obj->view_acc);
    }

    public function get_comm_acc($obj) {
        return($obj->comm_acc);
    }

    public function is_avtor($obj) {
        //поки що без перевірки залогыненості
        if ($_SESSION['uid'] == $obj->obj_avtor) {
            return(true);
        }
        return(FALSE);
    }

}
Не звертайте увагу на  $qr->prep($rawdata);,  $res = $rawdata->bind_param('i', $pid);, $qr->bindpar($res);... - то я на етапі вивчення параметризованих запитів виводив для себе повідоомлення про помилки (на якому етапі і яяка помилка сталась). І до сих пір воно мене рятує, якщо я, наприклад, щось в bind_param забуду або десь іще щось не так.


class Post {

    public function __construct($ddb, $pid) {
        //Запит до бд і заповнення класу даними
        $q = 'select uid_posts,postacc,postcomacc from posts where pid=?';
        $rawdata = $ddb->prepare($q);

        $qr = new qresult($ddb);
        $qr->prep($rawdata);
        $res = $rawdata->bind_param('i', $pid);
        $qr->bindpar($res);
        $res = $rawdata->execute();
        $qr->exec($res);
        $x = $qr->numrow($rawdata);

        if ($x != 1) {
            //нема поста
            $rawdata->close();
//            er(51);
            $e = new err();
            $e->er(51);
        }

        $res = $rawdata->bind_result($this->obj_avtor, $this->view_acc, $this->comm_acc);
        $qr->bindres($res);
        $res = $rawdata->fetch();
        $qr->fetch($res);
        $rawdata->close();
    }


class Foto {

    public function __construct($ddb, $fotoid) {
        //Запит до бд і заповнення класу даними
        $q = 'select uid_foto,albacc,albcomacc from alb join foto on albid=albid_foto where fotoid=?';
        $rawdata = $ddb->prepare($q);


        $qr = new qresult($ddb);
        $qr->prep($rawdata);
        $res = $rawdata->bind_param('i', $fotoid);
        $qr->bindpar($res);
        $res = $rawdata->execute();
        $qr->exec($res);
        $x = $qr->numrow($rawdata);

        if ($x != 1) {
            //фото нема
            $rawdata->close();
//            er(62);
            $e = new err();
            $e->er(62);
        }

        $res = $rawdata->bind_result($this->obj_avtor, $this->view_acc, $this->comm_acc);
        $qr->bindres($res);
        $res = $rawdata->fetch();
        $qr->fetch($res);
        $rawdata->close();
    }

 
}
Недолік такого підходу: $uid_posts та $uid_foto довелось обізвати $this->$obj_avtor




Як воно виглядає в коді:

Перевірка для поста        @$pid = $_GET['id'];  //Я знаю що напряму так не можна звертатись до $_GET, та іще й із @ попереду
        echo $pid;
       
        $post = new Post($ddb, $pid);
        $user_avtorize = new UserAvtorize($post);
       
        echo '<br><br>';
        echo ($user_avtorize->get_view_acc($post));
        echo '<br><br>';
        echo ($user_avtorize->get_comm_acc($post));
        echo '<br><br>';
        var_dump($user_avtorize->is_avtor($post));
        exit;
Ну і таке ж буде і для Фото
        $fotoid = $_GET['id'];
       
          echo $fotoid;
       
        $foto = new Foto($ddb, $fotoid);
        $user_avtorize = new UserAvtorize($foto);
       
        echo '<br><br>';
        echo ($user_avtorize->get_view_acc($foto));
        echo '<br><br>';
        echo ($user_avtorize->get_comm_acc($foto));
        echo '<br><br>';
        var_dump($user_avtorize->is_avtor($foto));
        exit;
       
Повторюсь: я не знаю що буде якщо далі продовжити кодування. Пізніше буде видно.
А в результаті що? Спочатку вище написав методи для кожного класу, потім через 1 годину придумав оце, що в цьому повідомленні написав... А що буде завтра???
Питання: що краще? те що я з UserAvtorize накалякав, чи те що з трьома методами для кожного об'єкта? І які у вас варіанти є???


Бо у мене є наступні питання, "проблеми". "Проблемні" питання.
« Змінено: 2019-09-19 02:22:46 від gdekjifgb »

тс

  • Гість
В echo наставив зайвих дужок. Але виправляти в повідомленні не хочу. На форумі є глюк пов'язаний з новим рядком. Боюсь що увесь текст стане безабзацним.

gdekjifgb

  • Гість
...
Але наведений код не поліморфізм(?). Це скоріше схоже на поліморфізм навпаки(?) Поки що почекаю ваших думок, потім далі почну "жалібливо жалітись"  ;) .
« Змінено: 2019-09-19 14:48:47 від gdekjifgb »

Відсутній lpi3

  • Новачок
  • *
  • дописів: 30
  • Карма: +0/-0
ну шо там? як там твої поліморфізми? розібрався?

gdekjifgb

  • Гість
ну шо там? як там твої поліморфізми? розібрався?
ну поліморфізм я зрозумів рік назад. Але в даному прикладі виходить "поліморфізм навпаки".

Що таке істинний поліморфізм?: От конкретно в цьому випадку - Класи Пост, Фото, Фотоальбом мають метод show. Десь в коді створюється об'єкт Пост, Фото або Фотоальбом. Цей об'экт передаєься в метод об'єкта User, що називається view(). В методі view() викликається метод shоw() переданого об'экта.
Ось приклад коду:        $u = new CreateUser();
        $User = $u->newUser($ddb);
        //Користувач створюється на початку скрипта десь далеко зверху//        ...
//        ...
//        ...
//        ...

        $Post = new Post($ddb, $pid);   //тут може створюватись пост,фото, фотоальб або щось інше
                                        //обов'зково з методом show()
       
        $User->view($Post);             //а тут метод view() містить рядок $obj->show();
                                        //Для чого так? Користувач може передивлятись потс, фото, фотоальбом.
                                        //а Пост, Фото, Фотоальбом можуть показувати себе

А в даному випадку (те зо я описав в попередніх повідомленнях) об'єкти Пост, Фото, Фотоальбом НЕ МАЮТЬ  методів get_view_acc(),
get_comm_acc(), is_avtor(). Оці перевірки авторства, гетери параметрів доступу реалізовані в окремому класі UserAvtorize, тому це не схоже на правильний науковий поліморфізм(?)


---

Питання було не тільки про правильність поліморфізму, а про правильність реалізації всього того про що я вище вже понаписував.


Я вже зрозумів що якщо продовжувати далі, то вийде страшніще ніж дизайн сайту. Доведеться самому продовжити написання неправильного коду, з подальшим переписуванням.

Повторюсь: питання було не тільки (і в основному НЕ) про поліморфізм.
« Змінено: 2019-09-23 17:43:42 від gdekjifgb »