问题描述
我正在尝试使用设计模式解决设计问题。现在我有了基础知识,我很确定我把它复杂化了很多。我似乎有多个空的接口,我可能可以用不同的设计做更少的事情。此外,我不确定该项目的未来开发人员是否会轻松解决这个问题。
我制作了类结构的模型。该示例简化为两种服务类型(参见 BaseAnimalService 扩展),在项目中还有更多。还有更多 BaseStrategy 实现。
首先,我想区分 CatService 或 DogService 的上下文。这是使用 BaseStrategy 类中的 Map 完成的,该类将 BaseAnimalService 作为值以启用 Cat/DogService 之间的多态性。基于在 Dog/CatStrategy 中实现的 BaseStrategy 的通用类型,使用了不同的 configurationMap,它依次根据条件的类型加载 Dog/CatService 的一个或另一个实现。 配置映射在 spring.xml 文件中定义。
由于 Dog/CatService 都实现了一个额外的接口,参见。 SomeOtherCat/DogService 是我设计的外部,Dog/CatService 也有空接口。 SomeOtherCatService 和 SomeOtherDogService 不相关且不可编辑,所以我不能多态地使用它们,这就是 Base/Cat/DogService 接口的原因。
我想过将 BaseStrategy 设为一个 StrategyFactory,它返回一个 Cat/DogStrategy,它反过来检查 BaseAnimalService 使用的标准的类型。但由于这两种策略对其策略使用相同的逻辑,这意味着我必须创建另一个基类。
你怎么看?关于这个问题的更好设计有什么建议吗?或者对当前的有什么改进?
class BaseStrategy<T extends BaseAnimalService> {
private ContextService contextService;
private Map<String,BaseAnimalService> configurationMap;
T getService() {
return configurationMap.get(contextService.getCurrentContext());
}
}
interface BaseAnimalService {
//empty
}
interface DogService extends BaseAnimalService {
//empty
}
interface CatService extends BaseAnimalService {
//empty
}
class DogStrategy extends BaseStrategy<DogService> {
//empty
}
class CatStrategy extends BaseStrategy<CatService> {
//empty
}
class BritishShortHairserviceImpl implements CatService,SomeOtherCatService {
@Override //source: SomeOtherCatService,same for other implementations below
void pur() {
//pur
}
}
class LionServiceImpl implements CatService,SomeOtherCatService {
@Override
void pur() {
//pur
}
}
class PitBullServiceImpl implements DogService,SomeOtherDogService {
@Override
void wagTail() {
//wag tail
}
}
class ChihuahuaServiceImpl implements DogService,SomeOtherDogService {
@Override
void wagTail() {
//wag tail
}
}
class CatPerson {
private BaseStrategy<CatService> catStrategy;
void pet() {
catStrategy.getService().pur();
}
}
class DogPerson {
private BaseStrategy<DogService> dogStrategy;
void Feed() {
dogStrategy.getService().wagTail();
}
}
相关 spring.xml 片段:
<bean id="baseStrategy" abstract="true"
class="com.animals.services.BaseStrategy">
<property name="contextService" ref="contextService"/>
</bean>
<bean id="catServiceStrategy"
class="com.animals.services.CatStrategyImpl"
parent="baseStrategy">
<property name="strategyConfigurationMap">
<map>
<entry key="CONTEXT1" value-ref="britishShortHairservice"/>
<entry key="CONTEXT2" value-ref="lionService"/>
</map>
</property>
</bean>
<bean id="dogServiceStrategy"
class="com.animals.services.DogStrategyImpl"
parent="baseStrategy">
<property name="strategyConfigurationMap">
<map>
<entry key="CONTEXT1" value-ref="pitbullService"/>
<entry key="CONTEXT2" value-ref="chihuahuaService"/>
</map>
</property>
</bean>
解决方法
我不熟悉 Spring 或其上下文服务模型,所以我是从一般的、独立于语言的 OOP 角度来解决这个问题的。
在我看来,您需要考虑通过构造函数(依赖注入)传递配置而不是基于映射进行切换的方式。您需要更多的“has a”关系(组合)和更少的“is a”关系(继承)。
AnimalService
可以将动物对象作为构造函数的参数。我们可以说 AnimalFeedbackBehavior
必须包含 positiveFeedback()
、neutralFeedback()
和 negativeFeedback()
的方法——但是这些方法的实现方式因动物而异。 Cat
会purr()
响应积极的互动,但 Dog
会wagTail()
。
AnimalOwner
可以feed()
任何动物并触发AnimalFeedbackBehavior.positiveFeedback()
。 AnimalOwner
不需要知道该行为在幕后做了什么。它甚至不需要知道它有什么种类的动物。它需要知道的只是这个方法存在。
interface AnimalFeedbackBehavior {
positiveFeedback(): void;
neutralFeedback(): void;
negativeFeedback(): void;
}
class AnimalOwner {
private animal: AnimalFeedbackBehavior;
// pass animal instance to the constructor
constructor( animal: AnimalFeedbackBehavior) {
this.animal = animal;
}
// trigger positive feedback when feeding
feed() {
this.animal.positiveFeedback();
}
}
class Cat implements AnimalFeedbackBehavior {
purr() {
//do something
}
positiveFeedback() {
this.purr();
}
/* ... rest of class ... */
}
这里我们假设 feed
总是积极的互动。但是,如果我们希望不同的动物对相同的互动有不同的反应呢? chase()
对于 Dog
可能是正数,但对于 Cat
可能是负数。一种天真的方法是根据地图切换反馈。但是理想的设计允许最大限度地抽象,其中 AnimalOwner
不需要了解任何关于动物类型的信息。
让我们尝试一个完全不同的设置。
如果您正在处理一小组行为,我们可以要求动物对每个行为做出反应,而不是积极/中性/消极。
interface AnimalBehavior {
feedResponse(): void;
chaseResponse(): void;
}
但这很快就会变得笨拙。我们可以使用 respond
方法定义一个动物,该方法响应某种通用操作对象。在实现中,它可以对动作做出响应,也可以直接忽略它。
这种设置还使多个覆盖行为的组合更加直观,因为我们可以遍历一系列 respond
函数,直到有人处理它为止。我们想知道是否有响应,因此我们需要从 response
函数返回一些内容。如果它基本上是 void
,那么我们可以返回一个 boolean
标志,如果它有响应,它就是 true
。如果响应应该返回一个值,那么您将返回该值或 undefined
。
interface Action {
type: string;
}
// we may want to attach some sort of data
interface ActionWithData<T> extends Action {
type: string;
data: T;
}
interface AnimalBehavior {
respond( action: Action ): string | undefined;
}
class Animal implements AnimalBehavior {
// an animal has an array of behavior responders
// as written,the earlier behaviors in the array override later ones
private behaviors: AnimalBehavior[];
// can instantiate an animal with multiple behaviors
constructor( behaviors: AnimalBehavior[] = [] ) {
this.behaviors = behaviors;
}
// can also add behaviors after the fact
public addOverride( behavior: AnimalBehavior ) {
this.behaviors = [behavior,...this.behaviors];
}
// loop through behaviors until one responds
public respond (action: Action): string | undefined {
for ( let element of this.behaviors ) {
// could be a response or might be undefined
const response = element.respond(action);
if ( response ) {
return response;
}
}
// could do something here if no behaviors responded
return undefined;
}
}
class AnimalOwner {
private animal: AnimalBehavior;
// pass animal instance to the constructor
constructor( animal: AnimalBehavior) {
this.animal = animal;
}
// animal can respond to the feed action,or not
feed(): string | undefined {
return this.animal.respond({type: 'feed'});
}
chase(): string | undefined {
return this.animal.respond({ type: 'chase' });
}
}
这些实现目前感觉很草率。现在他们都没有使用 this
,所以使用 class
毫无意义。但只是给你一个想法:
class DogBehavior implements AnimalBehavior {
respond(action: Action): string | undefined {
switch (action.type) {
case 'feed':
return "Wag Tail";
case 'chase':
return "Run Around";
default:
return undefined;
}
}
}
class PuppyBehavior implements AnimalBehavior {
respond(action: Action): string | undefined {
switch (action.type) {
case 'feed':
return "Jump";
default:
return undefined;
}
}
}
class ChihuahuaBehavior implements AnimalBehavior {
respond(action: Action): string | undefined {
switch (action.type) {
case 'feed':
return "Yip";
default:
return undefined;
}
}
}
Animal
组合和单个行为都实现了 AnimalBehavior
,因此 AnimalOwner
可以直接采用 DogBehavior
,也可以采用 Animal
组成DogBehavior
和其他一些行为。
const owner1 = new AnimalOwner(new DogBehavior());
let res = owner1.feed(); // is "Wag Tail"
顺序很重要。如果我们有一只吉娃娃小狗,我们需要决定 ChihuahuaBehavior
是否覆盖 PuppyBehavior
,反之亦然。
// prioritizes puppy
const owner2 = new AnimalOwner(new Animal([new PuppyBehavior(),new ChihuahuaBehavior(),new DogBehavior()]));
res = owner2.feed(); // is "Jump" from PuppyBehavior
res = owner2.chase(); // is "Run Around" from DogBehavior because not overwritten
// prioritizes chihuahua
const owner3 = new AnimalOwner(new Animal([new ChihuahuaBehavior(),new PuppyBehavior(),new DogBehavior()]));
res = owner3.feed(); // is "Yip" from ChihuahuaBehavior