Kiedy klasy dziedziczące mają takie same metody, to lepiej by dziedziczyły te metody od klasy nadrzędnej, czy miały te metody "przykazane" przez interface i umieszczone w sobie? Takie 2 rozwiązania pokazuję poniżej. Które jest bardziej poprawne?
//example 1 interface Person { function getName(); function setName($name); function getHobby(); function setHobby($hobby); } class Person2 implements Person{ private $name; private $hobby; public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } public function getHobby() { return $this->hobby; } public function setHobby($hobby) { $this->hobby = $hobby; } } class Person3 implements Person{ private $name; private $hobby; public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } public function getHobby() { return $this->hobby; } public function setHobby($hobby) { $this->hobby = $hobby; } } //example 1 end //example 2 abstract class Human{ protected $name; protected $hobby; public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } public function getHobby() { return $this->hobby; } public function setHobby($hobby) { $this->hobby = $hobby; } } class Human1 extends Human{ protected $name; protected $hobby; } class Human2 extends Human{ protected $name; protected $hobby; } //example 2 end //TESTING $Mark = new Person2(); $Mark->setName('Mark'); http://www.php.net/echo "{$Mark->getName()}<br>"; //Mark $Mark = new Human2(); $Mark->setName('Mark'); http://www.php.net/echo "{$Mark->getName()}<br>"; //Mark
W przykładzie, który podałeś klasy są identyczne dlatego nie ma sensu ich rozbijać na dwie. Jeśli część metod byłaby wspólna (dokładnie takie same metody łącznie z implementacją), wtedy te metody mogłyby być w klasie nadrzędnej, a w klasach pochodnych umieściłbyś różnice.
W twoim przypadku powinna to być jedna klasa. Czy interfejs byłby tam przydatny to zależy. Można by napisać więcej gdybyśmy znali szerszy kontekst, jaką aplikację piszesz i jak obiekty tej klasy są używane.
abstract class AbstractBook { abstract function getAuthor(); abstract function getTitle(); }
<?php /* * BookFactory classes */ abstract class AbstractBookFactory { abstract function makePHPBook(); abstract function makeMySQLBook(); } class OReillyBookFactory extends AbstractBookFactory { private $context = "OReilly"; function makePHPBook() { return new OReillyPHPBook; } function makeMySQLBook() { return new OReillyMySQLBook; } } class SamsBookFactory extends AbstractBookFactory { private $context = "Sams"; function makePHPBook() { return new SamsPHPBook; } function makeMySQLBook() { return new SamsMySQLBook; } } /* * Book classes */ abstract class AbstractBook { abstract function getAuthor(); abstract function getTitle(); } abstract class AbstractMySQLBook extends AbstractBook { private $subject = "MySQL"; } class OReillyMySQLBook extends AbstractMySQLBook { private $author; private $title; function __construct() { $this->author = 'George Reese, Randy Jay Yarger, and Tim King'; $this->title = 'Managing and Using MySQL'; } function getAuthor() { return $this->author; } function getTitle() { return $this->title; } } class SamsMySQLBook extends AbstractMySQLBook { private $author; private $title; function __construct() { $this->author = 'Paul Dubois'; $this->title = 'MySQL, 3rd Edition'; } function getAuthor() { return $this->author; } function getTitle() { return $this->title; } } abstract class AbstractPHPBook extends AbstractBook { private $subject = "PHP"; } class OReillyPHPBook extends AbstractPHPBook { private $author; private $title; private http://www.php.net/static $oddOrEven = 'odd'; function __construct() { //alternate between 2 books if ('odd' == self::$oddOrEven) { $this->author = 'Rasmus Lerdorf and Kevin Tatroe'; $this->title = 'Programming PHP'; self::$oddOrEven = 'even'; } else { $this->author = 'David Sklar and Adam Trachtenberg'; $this->title = 'PHP Cookbook'; self::$oddOrEven = 'odd'; } } function getAuthor() { return $this->author; } function getTitle() { return $this->title; } } class SamsPHPBook extends AbstractPHPBook { private $author; private $title; function __construct() { //alternate randomly between 2 books http://www.php.net/mt_srand((double)http://www.php.net/microtime() * 10000000); $rand_num = http://www.php.net/mt_rand(0, 1); if (1 > $rand_num) { $this->author = 'George Schlossnagle'; $this->title = 'Advanced PHP Programming'; } else { $this->author = 'Christian Wenz'; $this->title = 'PHP Phrasebook'; } } function getAuthor() { return $this->author; } function getTitle() { return $this->title; } }
Tak moimi słowami, klasę abstrakcyjną czy interfejs piszę często po to, aby mieć pewność, że dany obiekt posiada implementację danej metody.
Przykładowo masz np. klasę abstrakcyjną Storage obsługującą zapisywanie danych, po której dziedziczą np. klasy Database, Session, File itp. Każda z nich musi mieć metodę odczytującą i zapisującą. Więc w klasie nadrzędnej Storage umieszczam dwie metody write i read. Potem gdy, inna klasa będzie musiała przyjąć obiekt danych, to w metodzie rzutuję
I mam pewność że będę mógł odczytać i zapisać dane. Przykład taki z rękawa, może wiesz co mam na myśli.
function przyjmij(Storage $storage) {//}
Osobiście spotkałem się z podejściem, gdzie interfejsy służyły do zdefiniowania publicznego API konkretnej klasy obiektów (np Storage), natomiast klasa abstrakcyjna była podstawową implementacją tego interfejsu, zawężając jego zakres do podklasy tych obiektów (np DBStorage). Po tej klasie dziedziczyły już konkretne klasy, zmieniając jedynie to, co trzeba (np MySQLStorage).
Dzięki za odpowiedzi. Dla mnie wynika z tego, że nie ma "lepszego" sposobu, i wybór należy do programisty. Albo pokazuje on w klasie nadrzędnej lub interfejsie, jakie metody będą wymagane w kasach dziedziczących względnie implementujących interfejs, albo kieruje się rozsądkiem i stosuje dziedziczenie identycznych metod zamiast je powielać.
Oczywiście metoda 1. ma też swoje zalety, bo kod jest wtedy czytelniejszy.
To nie jest kwestia żadnego wyboru. Do czego innego służy interfejs, a do czego innego klasa abstrakcyjna. I nie masz tu żadnego wyboru, czy jeszcze lepiej "lepszego rozwiązania".
W interfejsach umieszczasz informację o tym jakie metody muszą być zawarte w klasie, która go implementuje i tyle, mogą to być zupełnie nie powiązane ze sobą klasy, które implementują ten sam interfejs. Na dodatek interfejs nie może zawierać żadnej funkcjonalności.
Klasa abstrakcyjna, może mieć jakąś funkcjonalność w sobie, a resztę zostawia klasom dziedziczącym po niej. Na dodatek każda klasa dziedzicząca, musi mieć z nią dość ścisły związek.
Podsumowując implementacja i dziedziczenie są od siebie niezależne i nic nie stoi na przeszkodzie, implementować interfejs i dziedziczyć po danej klasie jednocześnie. Czy też tylko implementować interfejs, czy też tylko dziedziczyć po klasie abstrakcyjnej.
Twój abstrakcyjny przykład jest bez sensu i nie rozumiesz idei obiektowości. Przykład z tutoriala jest sensowny, mógłbyś sobie do niego jeszcze napisać interfejsy i byłoby też dobrze, niektórzy powiedzą, że nawet lepiej.
Damonsson, nie rozumiesz pytania. Chodzi o dublowanie metod. W tutorialu metody są dublowane, a można tego tego uniknąć przez dziedziczenie tych metod z klasy nadrzędnej. Nie chodzi mi o róznice między abstract class a interface, bo jest mi dobrze znana. Z dyskusji powyżej wynika, że programista sam decyduje, czy lepiej będzie uzyskać czytelność kodu przez zastosowanie abstract class lub interface, czy zastosować dziedziczenie metod i uniknąć dublowania.
Ale Damansson dobrze Ci odpowiedział i według mnie nie zrozumialeś do końca jego wypowiedzi. Nijak się ma dublowanie metod bo w interface jest tylko ich czysta definicja. Klasa abstrakcyjna może zawierać część funkcjonalności wspólną dla wszystkich klas potomnych ale to do klas poszczególnych zależy jak te specyficzne metody (abstrakcyjne) obsłużą. Masz jeszcze cechy (traits) o których tu nic nie mówisz a które możesz dołączyć do kodu również eliminując powielanie.
Zobacz też interface w takim kontekście: https://github.com/RalfEggert/zend-expressive-tutorial/blob/part6/src/Album/Action/AlbumCreateFactory.php
O jakim dublowaniu mówisz. Bo tutaj jedynie dublowanie gettery.
Jeśli jednak pisząc ogólnie to:
klasa nadrzędna zawsze powinna zawierać zestaw metod wspólnych.
Czy to będzie klasa abstract czy zwyczajna to już zależy od preferencji.
Czy paretn ma być implements Intreface to też zależy.
Inny przypadek to jak ktoś wcześniej napisał Interface jako definicja metod (Przytoczony przykład z Storage).
trzczy, Damansson ma rację, w tutorialu żadne metody nie są dublowane. Wydaje się, że rzeczywiście nie zrozumiałeś jeszcze dobrze programowanie obiektowego.
AbstractFactory jest interfejsem czyli nie posiada implementacji. Zawiera tylko deklarację metod (deklarację nie definicję bo definicja to implementacja metody).
Każda klasa implementująca interfejs AbstractFactory definiuje metody zadeklarowane w tym interfejsie, ale nie jest to duplikacja. Dzięki temu masz dwie różne implementacje tego samego interfejsu. To znaczy możesz używać obiektu dowolnej z klas pochodnych w dokładnie ten sam sposób, chociaż każda klasa będzie zachowywać się trochę inaczej (czyli korzystasz z polimorfizmu).
Zaleta tego jest taka, że kod który używa obiektu dowolnej z klas implementujących ten interfejs nie musi brać pod uwagę, z jaką klasą konkretnie ma do czynienia. Kod przez to będzie prostszy w zrozumieniu, rozbudowie i testowaniu.
Ktoś u góry podawał już przykład z klasą Storage. Powiedzmy że tworzysz aplikację, która zapisuje jakieś wiadomości do plików lub bazy.
Kod nie był testowany i może zawierać błędy.
<?php interface StorageInterface { public function save($message); } class FileStorage implements StorageInterface { private $dirPath; public function __construct($dirPath) { $this->dirPath = $dirPath; // ustawiasz katalog(folder) w jakim zapisywać pliki } public function save($message) { // tu masz kod zapisujący do pliku } } class DbStorage implements StorageInterface { public function __construct($dbName, $userName, $passwd, $host) { // tu łączymy się z bazą $this->conn = connect_some_db($dbName, $userName, $passwd, $host); } public function save($message) { // tu zapisujemy do bazy } } abstract class AbstractRequestHandler { protected $storage; public function __construct(StorageInterface storage) { $this->storage = storage; } abstract public function handleRequest(Request $request); } class ARequestHandler extends AbstractRequestHandler { public function handleRequest(Request $request) { // tu pobieramy dane z requesta, generujemy wiadomość // i zapisujemy $this->storage->save($message); } } class BRequestHandler extends AbstractRequestHandler { public function handleRequest(Request $request) { // tu pobieramy dane z requesta // generujemy wiadomość w zupełnie inny sposób niż w klasie ARequestHandler // i zapisujemy $this->storage->save($message); } }
<?php // ... tu mamy trochę kodu, tworzymy obiekt Request itp. $storage = FileStorage('messages/'); $requestHandler = ARequestHandler($storage); $requestHandler->handleRequest($request);
public function save($message);
Settery i gettery to nie problem. O ile nie mają std. zadania to prawie każde IDE wygeneruje Ci taką listę.
Ad. definicji w programowaniu jest definicja/deklaracja i implementacja.
public function save($message);
W przykładzie gdzie implementujesz interfejs chodzi o to, że w interfejsie masz określone jakie metody musza być implementowane w danym obiekcie. W Twoim przykładzie pokazałeś dwa obiekty implementujące ten interfejs, a te obiekty są różne to że one zawierają te metody to nie oznacza że są identyczne, bo poniżej mogą zawierać inne metody które coś tam robią. Interfejs określa Ci czego potrzebujesz aby stworzyć dany obiekt który np. będzie wstrzykiwany do innej metody lub obiektu i wtedy będziesz miał wpisane na przykład tak :
public function example(TwojInterfejs $service){ .. }
Jeszcze się gubię w tym branżowym słownictwie...
Możesz nazwać jak chcesz np:
StorageInterface
StorageI
IStorage
InterfaceStorage
Dowolność, chodzi o to żeby wiedzieć że Obiekt docelowy wkładany do metody musi być typu lub implementować interface.
tak, tylko weź pod uwagę to że samego Interface nie możesz przekazać jako parametr. Zawsze to musi być klasa która implementuje dany typ lub rozszerza lub nim jest.
Nie było przypadkiem w którymś PSR że interface ma być przyrostkiem? Czyli StorageInterface.
Było
https://github.com/php-fig/fig-standards/blob/master/bylaws/002-psr-naming-conventions.md
Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)