良好的对象结构,可进行选择性组合,而不会发生类爆炸
我的真实场景有点难以解释,因此我将其映射到一个更容易识别的领域,例如家庭娱乐设备:
特定的设备可以提供不同的服务:
松下 XYZ 可以播放 DVD 和 CD。
索尼 ABC 只能播放 CD。
Hitachi PQR 可以播放 DVD 和接收电视。
...
...
每个服务(DVD、CD、TV...)都有大多数模型使用的默认实现,但某些模型具有特定服务的自定义版本。
接口
选择实现 DVD 可化、CD 可化、TV 可化……合同的模型将 导致模型之间存在大量代码重复。
单一继承
实现默认服务的单个超类将允许我为每个包含其所有自定义行为的模型拥有一个子类。然而,对于不提供所有类型服务的模型来说,我的超类将变得非常笨重和沉重。
多重继承
多重继承能够选择性地合并所需的服务并提供默认实现,从表面上看似乎很理想。我更看重将所有 PanasonicXYZ 自定义功能放在一个类中的内聚性,而不是继承引入的耦合。
但我没有使用 C++(而是 PHP),而且我觉得无论如何都有更好的方法。我也不想使用专有扩展,如 mixin 或 5.4 的特征。
组合
我看到类爆炸式增长,特定模型的自定义功能分散在多个类中 - 例如,我需要 PanasonicXYZ_CD 类和 PanasonicXYZ_DVD 类,并且它们只会被松下XYZ 对象。
有更好的结构吗?
编辑:我会仔细考虑一些评论和答案,而不是过早发表评论。
My real scenario is a bit hard to explain so I'll map it to a more recognizable domain, say home entertainment equipment:
A particular piece of equipment can offer different services:
A Panasonic XYZ can play DVDs and CDs.
A Sony ABC can only play CDs.
A Hitachi PQR can play DVDs and receive TV.
...
...
Each service (DVD, CD, TV,...) has a default implementation which most models use, but some models have customized versions of particular services.
Interfaces
Models choosing to implement DVD'izable, CD'izable, TV'izable,... contracts would
result in a lot of code duplication between models.
Single Inheritance
A single superclass implementing the default services would allow me to have a single subclass for each model containing all of its custom behaviour. My superclass would however be quite unwieldy and heavier than it need be for models that don't offer all types of services.
Multiple Inheritance
Multiple inheritance with its ability to selectively incorporate the services required and provide default implementations, on the surface seems ideal. I value the cohesiveness of having all PanasonicXYZ's custom functionality in a single class more than the coupling introduced by the inheritance.
But I'm not using C++ (rather PHP) and I sort of feel there's a better way anyway. Nor do I want to use proprietary extensions like mixins or 5.4's traits.
Composition
I see a class explosion with my custom functionality for a particular model scattered over multiple classes--I'd need a PanasonicXYZ_CD class and PanasonicXYZ_DVD class for example, and they'd only ever be used by the PanasonicXYZ object.
Is there a preferable structure?
Edit: I'll have a good think about some of the comments and answers made instead of prematurely commenting.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
使用组合 Luke:
对象类代表系统中的角色。即使考虑“设备”是很自然的事情,出于 OOP 的目的,最好考虑设备的角色:DVD 播放器、电视接收器、CD 播放器等。
单个设备完成所有操作并不重要其中,思考对象将具有的角色将帮助您结束单一责任对象:
通过这种方式很容易重构常见功能,您可以拥有一个
GenericDVDPlayer< /code> 并将其返回
asDVDPlayer
方法。如果您想允许更动态的使用,例如询问
Device
它支持哪些功能,您可以使用一种 Product Trader:在代码中您可以执行以下操作:
请注意,在这种情况下,“多功能设备”的行为就像 Product Trader
DVDPlayer.class
是产品的规格。有很多不同的方法来实现交易者和规范,其中一种是使用访客模式。但我发现在很多情况下(当你希望能够动态配置你的“多功能设备”时)你可以这样做:
这与构建器和流利的 API 相结合使得很容易定义不同的设备而不发生类爆炸:
Use composition Luke:
Object classes represents a role in your system. Even when is natural to think about a "Device", for OOP purposes is better to think about what are the roles of your device: DVD Player, TV Receiver, CD Player, etc.
It doesn't matter that a single device does all of them, thinking in the roles that the object is going to have will help you to end with single responsibility objects:
In that way is easy to refactor common functionality, you can have a
GenericDVDPlayer
and return that in theasDVDPlayer
method.If you want to allow a more dynamic usage, like asking to a
Device
which functionality it supports, you can use a kind of Product Trader, for example:and them in the code you could do something like this:
See that in this case the "multifunction device" acts like a Product Trader and the
DVDPlayer.class
is the spec of the product.There are a lot of different ways to implement the trader and the spec, one is to use the Visitor pattern. But I found that in a lot of cases (when you want to be able to configure your "multifunction devices" dynamically) you can do something like this:
This combined with a Builder and a fluent API makes easy to define different devices without class explosion:
我会使用依赖注入来做这样的事情:
然后所有设备都扩展
Devices
,所有函数都扩展Function
。要更改默认的Function
实现,您的设备类(例如SonyAbc)可以提供自己的方法,例如dvdPlay()
。I would use dependency injection to do something like this:
Then all devices extend
Devices
, all functions extendFunction
. To change a defaultFunction
implementation, your device class (e.g. SonyAbc) could provide its own methods for e.g.dvdPlay()
.我赞成《构图》。
您甚至需要每个设备的实际类吗?或者提供特定的电视作为具有某些功能的设备的正确接线实例就足够了吗?
可以通过调用来检索特征
I would favour Composition.
Do you even need actual Classes for each Device? Or would it be sufficient to provide a specific TV as a properly wired instance of a Device with some Features?
Features could be retrieved by invoking
这些服务是否已知且数量有限?
如果是这样,我会这样做:
为每种服务定义一个接口(例如 DvdPlayer、TvReceiver、CdPlayer 等...)
为每个服务接口提供一个默认实现(许多设备将共享的通用实现),并对您还需要的特定于模型的实现进行编码。
定义第二组接口来表示特定设备有能力提供服务(例如 DvdPlayerCapable),每个接口为服务接口声明一个(唯一命名的)getter,例如:
现在您的设备可以组合多个功能接口,并使用服务的默认实现或特定于模型的接口...
因此,如果您想在您的 Sony123Device(DvdPlayerCapable)上观看 DVD 第 15 章,您可以调用:
Are the services already known and limited in number?
If so I would do something like this :
Define an interface for each kind of service (e.g. DvdPlayer, TvReceiver, CdPlayer, etc...)
Give each service interface a default implementation (the common one that many devices will share) and code whichever model-specific implementations you also need.
Define a second set of interfaces to denote that a particular device has the capability to offer a service (e.g. DvdPlayerCapable), each interface declaring one (uniquely-named) getter for the service interface, for example:
Now your device can combine multiple capability interfaces, and use either the default implementation of the service or a model-specific one...
And so if for instance you want to go to dvd chapter 15 on your Sony123Device, which is a DvdPlayerCapable, you can call:
看来你有两个问题。第一个问题是如何在服务之间传递通用功能,这可以通过继承来解决。如下所示:
问题中有趣的部分是我们进入默认值的自定义版本。在这种情况下,我们不想使用继承,因为我们知道它直接违反了 LSP。因此,我们可以通过组合来处理它,这将导致类似于以下内容的结果:
或者您可以在实用程序类中公开常见特殊功能。
It seems you have two problems. The first problem is how do you pass around common functionality between services, that can be solved via inheritance. Something like the following:
The interesting piece of the problem is where we get into customized versions of the defaults. In this instance we would not want to use inheritance because we know it directly violates LSP. So we could approach it via composition which would result in something similar to the following:
Or you could expose the common special functionality inside of utility class.
过去,我在根抽象类上提供了通用的 Custom_Fuctions。它被序列化以进行存储,
然后我
通过方法调用添加海关:
In the past I have provided a generic Custom_Fuctions on my root abstract class. It is serialized for storage
I then call
add customs via method: