Является ли это правильным объектно-ориентированным программированием в php?

Может ли это быть классифицировано как правильное программирование ООП?

class Greeting { public $greet = array('Hi','Hello', 'Howzit', 'Ola', 'Whats up'); function __construct($name) { $this->name = $name; shuffle($this->greet); } } $hi = new Greeting('INSERTNAMEHERE'); /*NAME OF PERSON GOES HERE*/ echo $hi->greet[1] .' '. $hi->name; 

Ради сохранения его просто я бы сказал, что все в порядке. Это не слишком корректный OOP, хотя и не очень понятный код. Имея рабочий код лучше, чем вообще никакого кода.

Давайте рассмотрим ваш код:

 1 class Greeting { 2 3 public $greet = array('Hi','Hello', 'Howzit', 'Ola', 'Whats up'); 4 5 function __construct($name) { 6 $this->name = $name; 7 shuffle($this->greet); 8 } 9 } 

Строка 1: говорит, что этот класс представляет собой концепцию Приветствия. Что такое Приветствие? Я бы сказал что-то вроде «Hello John» или «Hi John» или «Howdy John» – это приветствие. И на самом деле вы, кажется, согласны, потому что …

Строка 3 : … у вас есть список похожих приветствий, просто без имени. Но это свойство требует ответа на вопрос, почему ваш класс называется Greeting, когда он действительно инкапсулирует несколько приветствий. Разве класс не следует называть «Приветствия» (помните о множественном числе)?

Строка 3 : Именование свойства «приветствие» тоже не слишком хорошая идея. Это свойство, поэтому не давайте ему имя глагола. Глаголы для методов.

Строка 3 : Хотя есть люди, которые расскажут вам разные, что делает публичную собственность редко хорошей идеей. Свойства – это внутреннее состояние, и это состояние не должно быть доступно напрямую, а только с помощью методов.

Строка 5 : Затем ваш конструктор говорит мне, что приветствие должно иметь имя. Если бы я уже не смотрел исходный код, я бы ошибочно предполагал, что это имя Приветствия. Но вы действительно имеете в виду имя человека. Аргумент должен отражать это и называться чем-то более показательным, например $ greetedPersonsName.

Строка 6 : Назначение свойств «на лету» – это boo boo. Если я посмотрю на определение класса, я хочу сразу увидеть свойства. Обнаружение их внутри некоторого метода затрудняет понимание кода. Это также не будет воспринято при создании документов API. Избегай это.

Строка 7 : shuffle – еще одна неожиданная вещь. Это неочевидный побочный эффект. Если бы я хотел создать новое приветствие, я ожидаю, что приветствия появятся в том порядке, в котором они перечислены. Это противоречит принципу наименьшего Astonishement, чтобы перетасовать их в ctor. Перетасовка должна происходить из общедоступного метода, который ничего не делает, кроме перетасовки, например

 public function shuffleGreetings() { shuffle($this->greetings); } 

Предполагая, что идея класса действительно была единственным приветствием, которое только инициализирует себя одним из возможных значений по умолчанию, мы также можем добавить Getter следующим образом:

 public function getGreeting() { return $this->_greetings[0] . ' ' . $this->name; } 

Это лучше, чем делать

 echo $hi->greet[1] .' '. $hi->name; 

потому что он скрывает детали реализации. Мне не нужно знать, что объект Приветствие имеет множество возможных приветствий. Я просто хочу получить приветствие в сочетании с заданным именем. Это все еще далека от совершенства, потому что вы все равно будете использовать его, как

 $hi = new Greeting('John'); // A Greeting named John? Why $hi then? $hi->shuffleGreetings(); // Shuffling Greetings in a Greeting? echo $hi->getGreeting(); // Why is it "Hello John" all of a sudden? 

Как вы можете видеть, API по-прежнему в значительной степени полон WTF. Разработчику все равно придется искать исходный код, чтобы понять, что происходит.

Подробнее о побочных эффектах

Хотя может возникнуть соблазн поставить shuffle в getGreeting вы не должны этого делать. Метод должен возвращать то же самое для одного и того же ввода. Когда я вызываю getGreeting дважды подряд, я могу ожидать, что он вернет тот же результат. Вы ожидали бы, что 1 + 1 будет возвращать 2 всегда, поэтому убедитесь, что ваши методы тоже работают.

Аналогичным образом, если вы хотите, чтобы один метод возвращал случайный элемент из свойства greetings, не перетасовывайте массив приветствий. Если вместо этого вы будете использовать метод shuffle, вы также измените свойство greetings. Это пульсирует с любой функцией, считанной из свойства, например, когда вы это делаете

 public function getRandomGreeting() { $this->shuffleGreetings(); return $this->getGreeting(); } 

разработчик испытает что-то вроде этого:

 $hi = new Greeting('John'); $hi->shuffleGreetings(); echo $hi->getGreeting(); // for example "Hello John" echo $hi->getRandomGreeting(); // for example "Hi John" echo $hi->getGreeting(); // for example "Howdy John" <-- WTF!! 

Используйте реализацию, которая не изменяет свойство, например

 public function getRandomGreeting() { $randomKey = array_rand($this->greetings); return $this->greetings[$randomKey] . ' ' . $this->name; } 

Это не имеет побочных эффектов:

 $hi = new Greeting('John'); $hi->shuffleGreetings(); echo $hi->getGreeting(); // for example "Hello John" echo $hi->getRandomGreeting(); // for example "Hi John" echo $hi->getGreeting(); // still "Hello John". Expected! 

Тем не менее, API все еще далек от симпатии. Если я думаю о свойствах Приветствия, я просто не думаю «Имя человека». Просто «Привет» или «Привет» по-прежнему является действительным приветствием. Он не требует имени. Как насчет

 public function greetPerson($personName) { return $this->getGreeting() . ' ' . $personName; } 

и тогда мы сможем

 $hi = new Greeting; $hi->shuffleGreetings(); echo $hi->greetPerson('John'); 

И, наконец, скрыть, что наше приветствие содержит массив, который нужно перетасовать, давайте переместим наш метод shuffleGreetings обратно в ctor и переименуем класс в RandomGreeting.

 class RandomGreeting … public function __construct() { $this->shuffleGreetings(); } 

Сначала это может показаться нелогичным, потому что я сказал вам не перетасовывать в ctor. Но с классом, переименованным в RandomGreeting, гораздо больше можно ожидать, что что-то происходит за кулисами. Нам просто не нужно знать, что именно. Чтобы отразить это, мы также должны сделать метод shuffleGreetings защищенным сейчас. Мы просто полностью скрываем его от открытого интерфейса. Теперь наш код читается следующим образом:

 $hi = new RandomGreeting; echo $hi->greetPerson('John'); // ex "Howdy John" 

Это не дает вам никаких WTF, потому что ваш код четко сообщает, что вы получите случайное приветствие. Имя класса ясно сообщает, что он делает.

Сейчас это немного лучше, и мы можем кончиться здесь, но можно все же утверждать, что приветствие не должно приветствовать его собственное, а скорее нечто, что делает Личность вместо этого.

Улучшение

Наши выводы должны привести нас к выводу, что приветствие скорее должно быть немым типом, инкапсулирующим приветственное сообщение, и ничего больше. Все, что он должен сделать, это вернуть это сообщение. Поскольку у Greeting нет никакого реального поведения рядом с сохранением строки сообщения, проще всего было бы создать объект Value, например объект, который равен другому объекту по значению свойства:

 class Greeting { protected $value; public function __construct($value) { $this->value = $value; } public function getValue() { return $this->value; } } 

Другой способ сделать это – сделать различные доступные приветствия отдельными типами. Этого очень мало, потому что у ваших объектов нет поведения. Вам нужно только это, если вы хотите воспользоваться Полиморфизмом. Тем не менее, наличие конкретных подтипов делает несколько дополнительных вещей, которые нужно рассмотреть позже, поэтому давайте предположим, что нам это нужно.

Правильный способ сделать это в ООП заключается в определении интерфейса

 interface Greeting { public function getGreeting(); } 

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

 abstract class GreetingType implements Greeting { protected $greeting; public function getGreeting() { return $this->greeting; } } 

Когда существует абстрактный класс, также должны быть конкретные классы, полученные из абстрактного класса. Поэтому давайте используем Inheritance для определения наших конкретных типов приветствий:

 class HiGreeting extends GreetingType { protected $greeting = 'Hi'; } class HelloGreeting extends GreetingType { protected $greeting = 'Hello'; } class HowdyGreeting extends GreetingType { protected $greeting = 'Howdy'; } 

Не обязательно иметь интерфейс и абстрактный интерфейс, реализующий интерфейс. Мы могли бы сделать наши конкретные приветствия не простирающимися от GreetingType. Но если бы мы только переопределили метод getGreeting во всех различных классах приветствия, мы бы дублировали код и были гораздо более склонны к внедрению ошибок, и если нам когда-либо понадобится что-то изменить, нам придется прикоснуться ко всем этим классам. С GreetingType все централизовано.

И наоборот. Вам не обязательно нужен интерфейс. Мы могли бы использовать только абстрактный тип. Но тогда мы ограничились GreetingType, тогда как с интерфейсом мы могли бы добавить новые типы с гораздо большей легкостью. Я признаю, что сейчас не могу придумать, так что это, вероятно, ЯГНИ . Но так мало добавить, что мы можем просто сохранить его сейчас.

Мы также добавим нулевой объект, который возвращает пустую строку. Об этом позже.

 class NullGreeting extends GreetingType { protected $greeting = ''; } 

Создание объектов

Поскольку я не хочу засорять new classname всем моим потребляющим классам и ввести связь , я буду использовать простой Factory вместо создания объекта капсулы:

 class GreetingFactory { public function createGreeting($typeName = NULL) { switch(strtolower($typeName)) { case 'hi': return new HiGreeting; case 'howdy': return new HowdyGreeting; case 'hello': return new HelloGreeting; default: return new NullGreeting; } } } 

Фабрика является одним из немногих фрагментов кода, в котором вы действительно можете использовать swich / case, не проверяя, можете ли вы заменить условный на полиморфизм .

Обязанность

С созданием объекта в пути мы можем, наконец, начать добавлять наш класс Greetings:

 class Greetings { protected $greetings; protected $nullGreeting; public function __construct(NullGreeting $nullGreeting) { $this->greetings = new ArrayObject; $this->nullGreeting = $nullGreeting; } public function addGreeting(Greeting $greetingToAdd) { $this->greetings->append($greetingToAdd); } public function getRandomGreeting() { if ($this->hasGreetings()) { return $this->_getRandomGreeting(); } else { return $this->nullGreeting; } } public function hasGreetings() { return count($this->greetings); } protected function _getRandomGreeting() { return $this->greetings->offsetGet( rand(0, $this->greetings->count() - 1) ); } } 

Как вы можете видеть, Приветствия на самом деле просто оболочка для ArrayObject. Он гарантирует, что мы не можем добавить ничего, кроме объектов, реализующих интерфейс Greeting для коллекции. И это также позволяет нам выбрать случайное приветствие из коллекции. Он также гарантирует, что вы всегда получаете приветствие от вызова getRandomGreeting, возвращая NullGreeting. Это потрясающе, потому что без этого вам придется делать

 $greeting = $greetings->getRandomGreeting(); if(NULL !== $greeting) { echo $greeting->getMessage(); } 

во избежание «Неустранимая ошибка: попытка вызова метода для не-объекта», когда метод getRandomGreeting не возвратил объект «Приветствие» (когда еще нет приветствия в классе Приветствия).

Класс не несет никакой другой ответственности . Если вы не уверены в том, что ваш класс слишком много или имеет методы, которые лучше должны быть на каком-то другом объекте, посмотрите на методы этого класса. Они работают с собственностью этого класса? Если нет, вы должны перенести этот метод .

Как это сделать

Теперь, чтобы, наконец, использовать весь этот код, мы добавим теперь класс Person. Поскольку мы хотим удостовериться, что можем называть метод getName, мы создаем интерфейс, прежде чем делать это

 interface Named { public function getName(); } 

Мы могли бы назвать этот интерфейс IPerson или что-то еще, но у него есть только один метод getName, и наиболее подходящее имя называется Named, потому что любой класс, реализующий этот интерфейс, является именованным, включая, но не ограничиваясь, наш класс Person:

 class Person implements Named { protected $name; protected $greeting; public function __construct($name, Greeting $greeting) { $this->name = $name; $this->greeting = $greeting; } public function getName() { return $this->name; } public function greet(Named $greetable) { return trim(sprintf( '%s %s', $this->greeting->getGreeting(), $greetable->getName() )); } } 

У нашего Человека есть требуемое имя, и мы требуем, чтобы он получил Приветствие. Все, что он может сделать, кроме возвращения, это имя приветствует другую Именованную вещь, скорее всего, другого человека. Вот и все.

Теперь все вместе:

 $greetings->addGreeting($greetingsFactory->createGreeting('Hi')); $greetings->addGreeting($greetingsFactory->createGreeting('Howdy')); $greetings->addGreeting($greetingsFactory->createGreeting('Hello')); $john = new Person('John Doe', $greetings->getRandomGreeting()); $jane = new Person('Jane Doe', $greetings->getRandomGreeting()); echo $john->greet($jane), PHP_EOL, $jane->greet($john); 

Живая демонстрация на Codepad

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

Хм, две вещи:

  • перетасовывание этого массива и доступ к нему со стороны, а внешний вызов зависит от перетасованного массива, не кажется правильным. Было бы лучше иметь метод, который возвращает результат перетасовки массива и, возможно, также имя сразу.

  • Было бы хорошим стилем объявить все свойства объекта заранее, чтобы его можно было документировать позже. Поэтому, если вы используете $this->name , вы должны объявить об этом.

Обе реализованные точки могут выглядеть так:

 class greeting { protected $greet = array('Hi','Hello', 'Howzit', 'Ola', 'Whats up'); protected $name = null; public function __construct($name) { $this->name = $name; } public function greet() { shuffle($this->greet); return $this->greet[1]." ".$this->name; } } 

с точки зрения новичков это было бы да, но поскольку это единственный объект, он не может быть ориентирован так, как вы могли бы с несколькими объектами.

True OOP – это когда вы отделяете отдельные объекты вашего приложения от объектов / зависимостей.

например, простой веб-сайт будет включать следующее:

  • База данных
  • Обработка ошибок
  • сессии
  • Безопасность

они называются сущностями и не должны взаимодействовать напрямую друг с другом, поэтому они разделяются.

поэтому, если мы хотим взаимодействовать с сеансами и безопасностью, чтобы сделать берег безопасным, мы добавим метод к объекту session для возврата стандартного результата PHP, такого как массив или строка, таким образом, многие объекты могут взаимодействовать друг с другом, не полагаясь на самом деле слишком много.

Глядя на вашу попытку в приветственном классе, я бы посмотрел на что-то вроде этого:

 class User { protected $data; public function __construct(array $user_data) { $this->data = $user_data; } public function getUsername() { return $this->data['username']; } } class Greeting { private $message = "welcome to mysite %s"; public function __construct(string $to_greet) { $this->message = sprintf($this->message,$to_greet); } public function getGreeting() { return $this->message; } } 

следующее будет использовано так:

 $User = new User(array( 'id' => 22, 'username' => 'Robert Pitt' )); $Greeting = new Greeting($User->getUsername()); echo $Greeting->getGreeting(); 

теперь я упомянул, что объекты взаимодействуют напрямую друг с другом, но если они делают это, они должны быть инкапсулированы таким образом, чтобы все объекты базы данных, такие как Database, Result, DB_Error, только взаимодействовали друг с другом.

это позволяет переносить код на другие проекты без значительной проблемы, также называемой библиотекой.

если объекты тесно связаны друг с другом, так как все они связаны друг с другом, вы можете сделать что-то вроде этого:

 $User = new User(/*..From DB ..*/); $Greeting = new Greeting($User); echo $Greeting->getGreeting(); 

Вероятно, было бы лучше иметь greet метод. Идея состоит в том, что пользователю не нужно знать, что $ this-> greet – это массив.

Так:

 class Greeting { protected $greet = array('Hi','Hello', 'Howzit', 'Ola', 'Whats up'); protected $name='You'; function __construct($name) { $this->name = $name; } function greet(){ shuffle($this->greet); echo "{$this->greet[0]} {$this->name}!"; } } $hi = new Greeting('INSERTNAMEHERE');/*NAME OF PERSON GOES HERE*/ $hi->greet(); 

Я должен не согласиться с [Изменить: Некоторые] другие ответы, которые вы получили. Сделать greet частным и добавить getter мало что значит для увеличения инкапсуляции.

Однако, несмотря на это, я думаю, что это довольно плохой дизайн OO. Объект должен представлять что-то (осмелюсь сказать, некоторые фактические предметы )? ИМО, «объекты» здесь будут действительно людьми (или что-то еще), один из которых приветствует другого. Самым приветствием должно быть сообщение, переданное от одного из этих объектов другому. Мы могли бы написать класс приветствия, чтобы сохранить текст приветствия, но даже если мы делаем сам объект приветствия, он должен быть независимым от источника или цели приветствия.

На самом деле это совсем не так. Прежде всего, общедоступные переменные, как правило, плохие. Вы не должны получать доступ к переменным класса непосредственно из кода. Вы должны попытаться инкапсулировать функциональность, предоставляемую классом, в сам класс.

Во-первых, позвольте мне показать вам, как хороший вид кода ООП мог бы выглядеть на вашем примере:

 <?php class greeting { private static $greets = array('Hi','Hello', 'Howzit', 'Ola', 'Whats up'); private $name; function __construct ($name) { $this->name = $name; } function doGreeting () { $i = array_rand(self::$greets); return self::$greets[$i] . ' ' . $this->name; } } $hi = new greeting('INSERTNAMEHERE');/*NAME OF PERSON GOES HERE*/ echo $hi->doGreeting(); 

Теперь поговорим о различиях здесь.

Во-первых, приветствия хранятся в статической переменной. Поскольку эти тексты, вероятно, не меняются экземпляром экземпляра, просто более эффективно хранить их в статической переменной, которая разделяется всем классом, поэтому каждый экземпляр не имеет собственной копии массива. Вы также заметите, что это личное. Это инкапсуляция. Код снаружи не обязательно должен знать о внутренних функциях класса, таких как имена переменных. Внешний код не должен знать о массиве, поскольку он должен использоваться только внутри самого класса.

Еще одна небольшая деталь заключается в том, что имя также объявлено там как private, поскольку все переменные класса должны быть частными в общем случае для инкапсуляции. Если им необходимо получить доступ, это должно быть выполнено через функции setter и getter, которые могут обеспечить дополнительную проверку значений, если это необходимо.

Поток кода также немного отличается. Вместо использования переменных класса непосредственно из внешнего кода мы получаем сам класс для создания приветствия и возвращаем его как строку. Таким образом, внешний код не должен знать, как создаются тексты приветствия или где имя хранится. Это должно знать API, то есть функции, их параметры и возвращаемые значения. Как они на самом деле работают, следует оставить для самого класса.

Кроме того, поскольку это, очевидно, функциональность, предназначенная для класса приветствия, делает код более многократно используемым, поскольку функциональность кодируется в класс. Часть ООП состоит в том, чтобы создать хороший многоразовый код, который можно использовать там, где это необходимо. Когда функциональность класса находится в самом классе, тот же код не требуется кодировать в другом месте.

Помните инкапсуляцию и повторное использование. Это два важных момента ООП.

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

Объект «лучше» будет выглядеть примерно так:

 class greeting { protected $greet = array('Hi','Hello', 'Howzit', 'Ola', 'Whats up'); protected $name; function __construct($name) { $this->name = $name; } public function greet() { return $this->greet[rand(0,5)] . ' ' . $this->name; } } $hi = new greeting('INSERTNAMEHERE');/*NAME OF PERSON GOES HERE*/ echo $hi->greet(); 

Теперь ваш объект greet полностью контролирует приветствие, и ваш код приложения не должен заботиться о том, как этот объект настроен, он просто должен заботиться о нескольких функциях, которые он может использовать для получения приветствия. Если вы хотите в конечном итоге интернационализировать или изменить приветствие каким-то образом, все, что вам нужно сделать, это изменить его на один объект.

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

он не является полностью правильным ООП, поскольку переменная $greet не инкапсулирована.

чтобы сделать его «более правильным» (что бы это ни значило), вам нужно будет сделать $greet param private и создать некоторые методы get и set для него (то же самое касается переменной $name которая также должна быть объявлена ​​до того, как она будет используемый).

в php метод get выглядит так:

если я хочу получить var:

 $hi = new greeting('INSERTNAMEHERE');/*NAME OF PERSON GOES HERE*/ echo $hi->__get('greet') .' '. $hi->__get('name'); 

get создается внутри класса приветствия:

 function __get($var){ return $this->$var; } 

и множество одинаково:

 function __set($var, $val){ $this->$var = $val; } 

Я бы, вероятно, использовал getters / setters для $ greet вместо того, чтобы публиковать его. Я не понимаю, почему это не будет считаться ООП.

http://en.wikipedia.org/wiki/Object-oriented_programming