Ответ 1
Ради Сохраняя это просто, я бы сказал, все в порядке. Это не слишком правильное ООП, хотя и не очень понятно понять код. Имея рабочий код лучше, чем отсутствие кода, хотя.
Пропустите свой код:
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, тогда как с интерфейсом мы могли бы добавить новые типы с гораздо большей легкостью. Я признаю, что сейчас не могу придумать, так что это, вероятно, YAGNI. Но это так мало, чтобы добавить, что мы можем просто сохранить его сейчас.
Мы также добавим Null Object, который возвращает пустую строку. Подробнее об этом позже.
class NullGreeting extends GreetingType
{
protected $greeting = '';
}
Создание объекта
Потому что я не хочу засорять new classname
все мои классы потребления и ввести coupling, я буду использовать простой 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;
}
}
}
Factory является одним из немногих фрагментов кода, в котором вы действительно можете использовать 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();
}
чтобы избежать "Fatal Error: Trying to call method on non-object", когда метод getRandomGreeting не возвратил объект Greeting (когда еще нет приветствия в классе Приветствия).
В классе нет другой ответственности. Если вы не уверены в том, что ваш класс слишком много или имеет методы, которые лучше должны быть на каком-то другом объекте, посмотрите на методы этого класса. Они работают с свойством этого класса? Если нет, вы должны, вероятно, переместите этот метод.
Выполнение
Теперь, чтобы, наконец, использовать весь этот код, мы добавим теперь класс 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);
Согласен, что довольно много кода для очень простой вещи. Некоторые вызовут это overengineered. Но вы спросили о правильном ООП, и, хотя я уверен, что есть еще возможности для улучшения, он вполне подходит и SOLID в моей книге. И это легко поддерживать сейчас, потому что обязанности ближе к тому, где они должны быть.