这是正确的面向对象的php编程吗? [英] Is this correct object oriented programming in php?

查看:41
本文介绍了这是正确的面向对象的php编程吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这可以归类为正确的OOP编程吗?

Could this be classified as correct OOP programming?

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;

推荐答案

为了保持简单,我会说没关系.但是,这不是太合适的面向对象操作,也不是特别易于理解的代码.有工作代码总比没有代码要好.

For the sake of Keeping it Simple, I'd say it's okay. It's not too proper OOP though, nor is it particularly easy to understand code. Having working code is better than having no code at all though.

让我们看一下您的代码:

Let's go through your code:

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行:说,此类代表了Greeting的概念.什么是问候语?我会说"Hello John","Hi John"或"Howdy John"之类的问候.实际上,您似乎同意,因为在...

Line 1: says this class represents the concept of a Greeting. What is a Greeting? I'd say something like "Hello John" or "Hi John" or "Howdy John" is a greeting. And in fact, you seem to agree because in …

第3行:…您确实有一个类似的问候列表,只是没有名字.但是,该属性提出了一个问题,即当您的类确实已经封装了多个问候语时,为什么将其命名为Greeting.那样的课不应该叫做问候语吗(请注意复数)?

Line 3: … you do have a list of similar greetings, just without a name. But that property warrants the question why your class is named Greeting, when it really encapsulates multiple greetings already. Shouldn't the class be called Greetings (mind the plural) then?

第3行:给属性"greet"命名也不是一个好主意.这是一个属性,所以不要给它加上动词的名称.动词是方法.

Line 3: Naming the property "greet" wasn't a too good idea either. It's a property, so dont give it the name of a verb. Verbs are for methods.

第3行:尽管有些人会告诉您不同的观点,但公开发布房地产绝不是一个好主意.属性是关于内部状态的,该状态不应直接访问,而只能通过方法访问.

Line 3: Although there is people who will tell you different, making the property public is rarely a good idea. Properties are about internal state and that state should not be accessible directly, but only through methods.

第5行:您的构造函数告诉我,Greeting必须有一个名称.如果我不会查看源代码,那么我会错误地认为这是Greeting的名称.但是,您的意思确实是一个人的名字.该参数应反映这一点,并应命名为更具指示性的名称,例如$ greetedPersonsName.

Line 5: Your constructor then tells me a Greeting has to have a name. If I wouldn't be looking at the source code already, I'd falsely assume this to be the name of the Greeting. But you really mean a Person's name. The argument should reflect that and be named something more indicative, like $greetedPersonsName.

第6行:动态分配属性是一个嘘声.如果查看类定义,我想立即查看属性.在某种方法中发现它们会使代码难以理解.生成API文档时,也不会出现这种情况.避免它.

Line 6: Assigning properties on the fly is a boo boo. If I look at the class definition, I want to see the properties immediately. Discovering them inside some method makes the code hard to understand. This will also not getting picked up when generating API docs. Avoid it.

第7行:shuffle是另一件事.这是一个不明显的副作用.如果我要实例化一个新的问候语,我希望这些问候语按列出的顺序显示.违反最小惊讶原则可以将他们拖到购物篮中.改组应该通过一种只能进行改组的公共方法进行,例如

Line 7: The shuffle is another unexpected thing. It's a non-obvious side-effect. If I was to instantiate a new Greeting, I'd expect the greetings to appear in the order they are listed. It's against the Principle of Least Astonishement to shuffle them in the ctor. The shuffling should happen from a public method that does nothing but shuffling, e.g.

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

假设该类的想法实际上是一个单独的Greeting,只是使用默认的可能值之一对其自身进行了初始化,我们还可以添加如下的Getter:

Assuming the idea of the class was really to be a single Greeting that just initializes itself with one of the default possible values, we can also add a Getter like this:

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

这比做得好

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

因为它隐藏了实现细节.我不需要知道Greeting对象具有一系列可能的问候.我只想将Greeting与设置的名称结合在一起.但是,它仍然离完美还很远,因为您仍然会像这样使用它

because it hides the implementation details. I don't need to know that the Greeting object has an array of possible greetings. I just want to get the Greeting combined with the set name. It's still far from perfect though, because you still would use it like

$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.开发人员仍然必须查看您的源代码以了解发生了什么.

As you can see, the API is still pretty much full of WTF. A developer will still have to look at your source code to understand what's going on.

尽管将shuffle放入getGreeting可能很诱人,但您不应该这样做.方法应该为相同的输入返回相同的事物.当我连续两次调用getGreeting时,可以期望它返回相同的结果.您可能希望1 + 1总是返回2,因此请确保您的方法也这样做.

While it may be tempting to put the shuffle into getGreeting you should not do so. A method should return the same thing for the same input. When I call getGreeting twice in a row, I can expect it to return the same result. You would expect 1+1 to return 2 always, so make sure your methods do too.

同样,如果您希望有一个方法可以从greetings属性中返回随机项目,请不要随机播放greetings数组.如果要改用shuffle方法,则还应更改greetings属性.这会波及到从属性读取的任何函数,例如当你做

Likewise, if you want to have a single method to return a random item from the greetings property, dont shuffle the greetings array. If you would use the shuffle method instead, you would also change the greetings property. That ripples to any function reading from the property, e.g. when you do

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

开发人员将遇到以下情况:

a developer will experience something like this:

$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!!

使用不更改属性的实现,例如

Use an implementation that doesnt change the property, e.g.

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仍然远远不够.如果我考虑问候语的属性,我只是不认为人名".只是说嗨"或你好"仍然是有效的问候.它不需要名称.怎么样

The API is still far from pretty though. If I think about the properties of a Greeting, I just dont think "Person's name". Just saying "Hi" oder "Hello" is still a valid greeting. It doesn't require a name. How about

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

然后我们可以做

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

最后要掩盖我们的Greeting包含需要改组的数组,让我们将shuffleGreetings方法移回ctor并将类重命名为RandomGreeting.

And to finally hide that our Greeting contains an array that needs to be shuffled, let's move our shuffleGreetings method back to the ctor and rename the class to RandomGreeting.

class RandomGreeting …

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

乍一看这似乎是违反直觉的,因为我告诉过你不要在ctor中洗牌.但是将类重命名为RandomGreeting之后,幕后发生了很多事情.我们只是不需要确切地知道什么.为了反映这一点,我们还应该使shuffleGreetings方法现在受到保护.我们只是将其完全隐藏在公共界面之外.现在我们的代码是这样的:

This might seem counterintuitive at first, because I told you not to shuffle in the ctor. But with the class renamed to RandomGreeting, it is much more to be expected that there is something happening behind the scenes. We just don't need to know what exactly. To reflect that, we should also make the shuffleGreetings method protected now. We just hide it from the public interface completely. Now our code reads like this:

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

这不会给您任何WTF,因为您的代码清楚地传达了信息,您会得到随机的问候.类名清楚地传达了它的作用.

This doesn't give you any WTFs because your code clearly communicates you'll get a random greeting. The classname clearly communicates what it does.

现在打赌更好,我们可以在这里结束,但是仍然可以说问候语不能独自打招呼,而是由人来代替.

This is a little bet better now and we could end here, but one could still argue that a Greeting should not be able to greet on it's own, but is rather something that is done by a Person instead.

我们的发现应该得出这样的结论,即问候语应该是封装问候语信息的愚蠢类型,而不是其他任何东西.它应该做的就是返回该消息.由于问候语在存储消息字符串旁边没有任何实际行为,因此最简单的方法是创建一个值对象,例如一个通过属性值等于另一个对象的对象:

Our findings should lead us to the conclusion that a Greeting should rather be a dumb Type encapsulating the Greeting message and nothing else. All it should do is return that message. Since the Greeting doesn't have any real behavior next to storing the message string, the easiest would be to create a Value Object, e.g. an object that is equal to another object by value of a property:

class Greeting
{
    protected $value;

    public function __construct($value)
    {
        $this->value = $value;
    }

    public function getValue()
    {
        return $this->value;
    }
}

另一种方法是将各种可用的问候变成单独的类型.当您的对象没有行为时,这样做几乎没有收获.仅在要利用多态性时才需要.但是,拥有具体的子类型确实会带来一些其他问题,供以后考虑,因此,让我们假设我们需要它.

The other way to do it would be to make the various available greetings into separate types. There is very little gain to do that when your objects don't have behavior. You only need that if you want to take advantage of Polymorphism. However, having concrete subtypes does draw a few additional things to consider later on, so let's just assume we need it.

在OOP中执行此操作的正确方法是定义接口

The proper way to do that in OOP would be to define an interface

interface Greeting
{
    public function getGreeting();
}

定义了一个想要表现得像Greeting的类,必须具有getGreeting方法.由于接口没有实现任何逻辑,因此我们还添加了抽象类型,其中包含问候属性和返回它的逻辑:

that defines a class that wants to behave like a Greeting has to have a getGreeting method. Since interfaces dont implement any logic, we also add an abstract type that contains the greeting property and the logic to return it:

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

当有一个抽象类时,还需要有从抽象类派生的具体类.因此,让我们使用继承定义我们的具体问候类型:

When there is an abstract class, there also needs to be concrete classes derived from the abstract class. So let's use Inheritance to define our concrete Greeting types:

class HiGreeting extends GreetingType
{
    protected $greeting = 'Hi';
}

class HelloGreeting extends GreetingType
{
    protected $greeting = 'Hello';
}

class HowdyGreeting extends GreetingType
{
    protected $greeting = 'Howdy';
}

并不一定必须要有一个接口和一个抽象来实现该接口.我们本来可以使我们的具体问候语不从GreetingType扩展而来.但是,如果我们只是在所有各种Greeting类上重新实现了getGreeting方法,我们将复制代码,并且更容易引入错误,并且如果我们需要更改某些内容,则必须触摸所有这些类.有了GreetingType,一切都集中了.

It's not absolutely necessary to have an Interface and an Abstract implementing the interface. We could have made our concrete Greetings not extend from the GreetingType. But if we had just reimplemented the getGreeting method on all the various Greeting classes, we would be duplicating code and were much more prone to introducing errors and should we ever need to change something, we'd have to touch all these classes. With the GreetingType it's all centralized.

反之亦然.您不一定需要一个接口.我们本可以只使用抽象类型.但是那时我们将被限制为GreetingType,而有了一个接口,我们可能可以更轻松地添加新的Types......................................................................................................................................................的方式的余地."...............................................................................一些.我承认我现在想不起任何东西,因此它可能是YAGNI .但是添加的内容很少,我们现在就可以保留它.

The other way round is true as well. You dont necessarily need an interface. We could have used just the abstract type. But then we would be limited to the GreetingType whereas with an interface, we could potentially add new Types with much more ease. I admit I can't think of any right now, so it is probably YAGNI. But it's so little to add that we can just as well keep it now.

我们还将添加一个空对象,该返回空字符串.以后再说吧.

We will also add a Null Object that returns an empty string. More on that later.

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

对象创建

因为我不想在我的所有消费类中乱扔new classname并引入工厂代替封装对象的创建:

Object creation

Because I do not want to litter new classname all over my consuming classes and introduce coupling, I will a use simple Factory instead to capsule object creation:

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而不需要检查是否可以用多态替换条件.

The Factory is one of the few pieces of code where you actually can use a swich/case without having to check if you can Replace Conditional with Polymorphism.

通过创建对象的方式,我们终于可以开始添加Greetings类:

With object creation out of the way, we can finally start to add our Greetings class:

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)
        );
    }
}

如您所见,Greetings实际上只是ArrayObject的包装.它可以确保除了将实现Greeting接口的对象添加到集合之外,我们不能添加其他任何东西.而且它还允许我们从集合中随机选择问候语".它还可以确保您始终通过返回NullGreeting来从对getRandomGreeting的调用中获得Greeting.这太棒了,因为没有它,您将不得不做

As you can see, Greetings is really just a wrapper for an ArrayObject. It makes sure that we cannot add anything else but objects implementing the Greeting interface to the collection. And it also allows us to pick a random Greeting from the collection. It also makes sure that you always get a Greeting from the call to getRandomGreeting by returning the NullGreeting. This is awesome, because without that you would have to do

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

为了避免在getRandomGreeting方法未返回Greeting对象时(当Greetings类中还没有Greeting时)发生致命错误:尝试在非对象上调用方法".

in order to avoid a "Fatal Error: Trying to call method on non-object" when the getRandomGreeting method did not return a Greeting object (when there is no Greeting in the Greetings class yet).

该班除了没有其他责任.如果不确定类是否做得太多或方法是否应该放在其他对象上,请查看该类中的方法. 它们是否可以使用该类的属性?您可能应该移动该方法.

The class has no other responsibility than that. If you are unsure if your class is doing too much or has methods that should better be on some other object, look at the methods in that class. Do they work with a property of that class? If not, you should probably move that method.

现在要最终使用所有代码,我们现在添加Person类.由于我们要确保可以在其上调用getName方法,因此我们先创建一个接口

Now to finally put all that code to use, we add our Person class now. Since we want to make sure we can call a getName method on it, we create an interface before doing so

interface Named
{
    public function getName();
}

我们可以命名该接口为IPerson或其他名称,但是它只有一个方法getName,然后最合适的名称为Named,因为实现该接口的任何类都是已命名的事物,包括但不限于我们的Person类:

We could have named that interface IPerson or whatever but it only has one method getName and the most fitting name is Named then, because any class implementing that interface is a named thing, including, but not limited to our Person class:

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()
        ));
    }
}

我们的人有一个必填名称,我们也要求它也有一个问候语.除了返回名字外,它所能做的就是迎接另一个具名的事物,可能是另一个人.就是这样.

Our Person has a required name and we demand it to have a Greeting as well. All it can do besides returning it's name is greet another Named thing, likely another person. And that's it.

现在将它们放在一起:

$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);

在键盘上进行实时演示

要完成一件非常简单的事情,需要提供大量代码.有些人会称之为过度设计.但是您要求正确的OOP,虽然我确定仍有改进的余地,但这是非常适当的,并且 SOLID .而且现在很容易维护,因为责任已经接近应有的职责.

Granted that's quite a lot of code for a very simple thing to do. Some will call it overengineered. But you asked for correct OOP and while I am sure there is still room for improvement, it's quite proper and SOLID in my book. And it's easy to maintain now, because the responsibilities are closer to where they should be.

这篇关于这是正确的面向对象的php编程吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆