这可以归类为正确的OOP编程吗?
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;
Run Code Online (Sandbox Code Playgroud)
Gor*_*don 79
为了保持简单,我会说没关系.虽然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 }
Run Code Online (Sandbox Code Playgroud)
第1行:说这个类代表了一个问候的概念.什么是问候语?我会说像"你好约翰"或"嗨约翰"或"你好约翰"这样的问候.事实上,你似乎同意,因为......
第3行:...你有一个类似的问候清单,没有名字.但是这个属性保证了为什么你的班级被命名为问候语的问题,当它真正封装了多个问候语时.这个班不应该被称为问候(记住复数)吗?
第3行:命名属性"问候"也不是一个好主意.这是一个属性,所以不要给它一个动词的名字.动词是用于方法的.
第3行:虽然有人会告诉你不同,但公开财产很少是一个好主意.属性是关于内部状态的,并且该状态不应该直接访问,而只能通过方法访问.
第5行:然后你的构造函数告诉我一个Greeting必须有一个名字.如果我不会查看源代码,我会错误地认为这是问候语的名称.但你真的是指一个人的名字.这个论点应该反映出来并被命名为更具指示性的东西,比如$ greetedPersonsName.
第6行:动态分配属性是一种嘘声.如果我查看类定义,我想立即看到属性.在某些方法中发现它们会使代码难以理解.在生成API文档时,这也不会被提起.躲开它.
第7行:这shuffle是另一个意想不到的事情.这是一个非明显的副作用.如果我要实例化一个新的问候语,我希望问候语按照它们列出的顺序出现.在ctor中将它们洗牌是违反最小原则的原则.改组应该是一种公共方法,除了改组之外什么都不做,例如
public function shuffleGreetings()
{
shuffle($this->greetings);
}
Run Code Online (Sandbox Code Playgroud)
假设这个类的想法真的是一个单独的问候语,只是用一个默认的可能值初始化自己,我们也可以像这样添加一个Getter:
public function getGreeting()
{
return $this->_greetings[0] . ' ' . $this->name;
}
Run Code Online (Sandbox Code Playgroud)
这比做好
echo $hi->greet[1] .' '. $hi->name;
Run Code Online (Sandbox Code Playgroud)
因为它隐藏了实现细节.我不需要知道Greeting对象有一系列可能的问候语.我只想将问候语与设定名称结合起来.它仍然远非完美,因为你仍然会喜欢它
$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?
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,API仍然充满了WTF.开发人员仍然需要查看源代码以了解正在发生的事情.
虽然它可能是很有诱惑力把shuffle到getGreeting你不应该这样做.对于相同的输入,方法应该返回相同的内容.当我getGreeting连续两次调用时,我可以期望它返回相同的结果.你会期望1 + 1总是返回2,所以确保你的方法也是如此.
同样,如果您希望使用单个方法从greetings属性返回随机项,请不要随意更改greetings数组.如果您使用shuffle方法,您还可以更改问候语属性.这会掠过从房产中读取的任何功能,例如当你这样做时
public function getRandomGreeting()
{
$this->shuffleGreetings();
return $this->getGreeting();
}
Run Code Online (Sandbox Code Playgroud)
开发人员会遇到这样的事情:
$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!!
Run Code Online (Sandbox Code Playgroud)
使用不会更改属性的实现,例如
public function getRandomGreeting()
{
$randomKey = array_rand($this->greetings);
return $this->greetings[$randomKey] . ' ' . $this->name;
}
Run Code Online (Sandbox Code Playgroud)
这没有副作用:
$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!
Run Code Online (Sandbox Code Playgroud)
虽然API仍然很不错.如果我想一下问候语的属性,我就不会想到"人的名字".只说"嗨"或"你好"仍然是一个有效的问候语.它不需要名称.怎么样
public function greetPerson($personName)
{
return $this->getGreeting() . ' ' . $personName;
}
Run Code Online (Sandbox Code Playgroud)
然后我们就可以做到
$hi = new Greeting;
$hi->shuffleGreetings();
echo $hi->greetPerson('John');
Run Code Online (Sandbox Code Playgroud)
最后隐藏我们的Greeting包含一个需要改组的数组,让我们将shuffleGreetings方法移回ctor并将类重命名为RandomGreeting.
class RandomGreeting …
public function __construct()
{
$this->shuffleGreetings();
}
Run Code Online (Sandbox Code Playgroud)
起初这似乎有点违反直觉,因为我告诉过你不要在ctor中洗牌.但是,如果将类重命名为RandomGreeting,那么可以预期幕后会发生一些事情.我们根本不需要知道究竟是什么.为了反映这一点,我们现在也应该保护shuffleGreetings方法.我们完全将它从公共界面隐藏起来.现在我们的代码如下所示:
$hi = new RandomGreeting;
echo $hi->greetPerson('John'); // ex "Howdy John"
Run Code Online (Sandbox Code Playgroud)
这不会给你任何WTF,因为你的代码清楚地告知你会得到一个随机问候.classname清楚地传达了它的功能.
现在这是一个更好的赌注,我们可以在这里结束,但人们仍然可以争辩说,问候语不应该能够自己问候,而是由人来代替.
我们的研究结果应该让我们得出结论,问候语应该是一个愚蠢的类型来封装问候消息而不是其他任何东西.它应该做的只是返回那条消息.由于Greeting在存储消息字符串旁边没有任何实际行为,因此最简单的方法是创建一个Value Object,例如,按属性值等于另一个对象的对象:
class Greeting
{
protected $value;
public function __construct($value)
{
$this->value = $value;
}
public function getValue()
{
return $this->value;
}
}
Run Code Online (Sandbox Code Playgroud)
另一种方法是将各种可用的问候语分成不同的类型.当你的对象没有行为时,没有什么好处.如果你想利用多态性,你只需要它.但是,具体的子类型确实会在以后考虑一些额外的事情,所以让我们假设我们需要它.
在OOP中执行此操作的正确方法是定义接口
interface Greeting
{
public function getGreeting();
}
Run Code Online (Sandbox Code Playgroud)
定义一个想要表现得像一个问候的类必须有一个getGreeting方法.由于接口不实现任何逻辑,我们还添加一个包含greeting属性的抽象类型和返回它的逻辑:
abstract class GreetingType implements Greeting
{
protected $greeting;
public function getGreeting()
{
return $this->greeting;
}
}
Run Code Online (Sandbox Code Playgroud)
当有抽象类时,还需要从抽象类派生的具体类.所以让我们使用继承来定义我们具体的Greeting类型:
class HiGreeting extends GreetingType
{
protected $greeting = 'Hi';
}
class HelloGreeting extends GreetingType
{
protected $greeting = 'Hello';
}
class HowdyGreeting extends GreetingType
{
protected $greeting = 'Howdy';
}
Run Code Online (Sandbox Code Playgroud)
实现接口的接口和抽象并不是绝对必要的.我们本可以使我们的具体问候语不从GreetingType扩展.但是如果我们刚刚在所有各种Greeting类上重新实现了getGreeting方法,那么我们就会复制代码并且更容易引入错误,如果我们需要更改某些内容,我们必须触及所有这些类.使用GreetingType,它全部集中.
反过来也是如此.你不一定需要一个界面.我们本可以只使用抽象类型.但是我们将限制在GreetingType中,而使用接口,我们可以更轻松地添加新类型.我承认我现在没想到,所以可能是YAGNI.但是补充一点,我们现在也可以保留它.
我们还将添加一个返回空字符串的Null对象.稍后会详细介绍.
class NullGreeting extends GreetingType
{
protected $greeting = '';
}
Run Code Online (Sandbox Code Playgroud)
因为我不想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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
工厂是您可以使用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)
);
}
}
Run Code Online (Sandbox Code Playgroud)
如您所见,Greetings实际上只是ArrayObject的包装器.它确保除了向集合实现Greeting接口的对象之外,我们不能添加任何其他内容.它还允许我们从集合中挑选一个随机的问候语.它还可以确保您通过返回NullGreeting从getRandomGreeting的调用中获得问候语.这太棒了,因为没有它你就必须这样做
$greeting = $greetings->getRandomGreeting();
if(NULL !== $greeting) {
echo $greeting->getMessage();
}
Run Code Online (Sandbox Code Playgroud)
当getRandomGreeting方法没有返回Greeting对象时(当Greetings类中没有Greeting时),以避免"致命错误:尝试在非对象上调用方法".
班级没有其他责任.如果您不确定您的类是否做得太多,或者方法应该更好地用于其他对象,请查看该类中的方法.他们是否与该班级的财产合作?如果没有,你应该移动那个方法.
现在最后要使用所有代码,我们现在添加Person类.由于我们要确保可以在其上调用getName方法,因此我们先创建一个接口
interface Named
{
public function getName();
}
Run Code Online (Sandbox Code Playgroud)
我们可以将接口命名为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()
));
}
}
Run Code Online (Sandbox Code Playgroud)
我们的人员有一个必需的名字,我们要求它也有一个问候语.除了返回它的名字之外,所有它可以做的是迎接另一个被命名的东西,可能是另一个人.就是这样.
现在把它们放在一起:
$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);
Run Code Online (Sandbox Code Playgroud)
认为这是一个非常简单的事情的相当多的代码.有些人称之为过度工程.但是你要求正确的OOP,虽然我确信仍有改进的余地,但在我的书中这是非常正确和坚固的.现在很容易维护,因为责任更接近应有的位置.
嗯,两件事:
改组那个数组并从外部访问它,并且让外部调用依赖于被洗牌的数组,感觉不对.有一个返回数组混乱结果的方法会更好,也可能就是这个名字.
预先声明所有对象属性将是一种很好的方式,因此可以在以后记录.所以如果你使用$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;
}
}
Run Code Online (Sandbox Code Playgroud)
从初学者的角度来看,这将是肯定的,但由于这是一个单一的对象,它不能像你可以用几个对象那样定向.
真正的OOP是指将应用程序的各个实体分隔为对象/依赖项.
例如,一个简单的网站将包括以下内容:
这些被称为实体,不应该直接相互作用,这就是为什么分开.
因此,如果我们想要交互Sessions和Security以确保会话是安全的,我们会向会话对象添加一个方法来返回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;
}
}
Run Code Online (Sandbox Code Playgroud)
以下将使用如下:
$User = new User(array(
'id' => 22,
'username' => 'Robert Pitt'
));
$Greeting = new Greeting($User->getUsername());
echo $Greeting->getGreeting();
Run Code Online (Sandbox Code Playgroud)
现在我已经提到对象确实直接相互交互,但是如果它们这样做,它们应该被封装,以便所有数据库对象,如Database,Result,DB_Error只会相互交互.
这允许代码可以传输到其他项目,而不必担心很多问题,也称为库.
如果这些对象是完全相关的,那么捆绑在一起就可以做到这样的事情:
$User = new User(/*..From DB ..*/);
$Greeting = new Greeting($User);
echo $Greeting->getGreeting();
Run Code Online (Sandbox Code Playgroud)