设计模式过于复杂



我正在尝试用设计模式解决一个设计问题。现在我有了基础,我很确定我把它弄得太复杂了。我似乎有多个空界面,我可能可以用不同的设计来做得更少。我也不确定未来项目的开发人员是否能很容易地解决这个问题。

我已经做了一个类结构的模型。该示例被简化为两种服务类型(参见BaseAnimalService扩展),在项目中还有更多。还有更多的BaseStrategy实现。

首先,我想区分CatService和DogService的上下文。这是使用BaseStrategy类中的Map完成的,该类将BaseAnimalService作为值,以启用猫/狗服务之间的多态性。基于在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角度来处理这个问题。

在我看来,你需要考虑通过构造函数传递配置的方法(依赖注入),而不是基于映射切换。你需要更多的"有"关系(构成)与"少"是"多";关系(继承).

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 ... */
}

Typescript Playground Link

这里我们假设feed总是一个正相互作用。但如果我们想让不同的动物对相同的互动产生不同的反应呢?chase()可能对Dog呈阳性,但对Cat呈阴性。naïve的一种方法是根据地图切换反馈。但理想的设计允许最大程度的抽象,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,也可以取由DogBehavior和其他行为组成的Animal

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

Typescript Playground Link

最新更新