分享

常用设计模式-注册器模式(二)

 景昕的花园 2024-01-06 发布于北京

前言

我常常觉得人们低估了设计模式的作用和意义。它们不仅是简历上的金边、程序员的黑话,也不仅是常见业务的常用处理方式或经验总结。

设计模式不仅是这些,它们更是面向对象思想理论结合实践的切入点。我们前面聊过抽象、高内聚低耦合、封装继承多态、SOLID设计原则。它们更偏理论指导,离编码实践还有一段距离。而这里要聊的设计模式,不仅有扎实的理论基础,而且实实在在地俯下身子、扎根到了实践当中。

从编码实践的角度来讲设计模式,这类文章没有一万也有八千。细抠几种设计模式之间的区别,这类文章写再多也没太大意义。这里就不凑这些热闹了。

这里,我会简单聊聊几个主要设计模式的编程应用,然后把主要精力放在它们与面向对象思想的关联上。另外,在聊过几种设计模式之后,计划提供一种复合模式,作为我使用设计模式的“最佳实践”,供读者参考。


注册器模式(一)

参见:常用设计模式-注册器模式(一)


注册器模式与面向对象

有心的朋友也许已经发现了:如果削去注册服务、只保留保存服务和取用服务的话,注册器模式就会退化为工厂模式。工厂模式与面向对象,在此前就已经讨论过了。因此,这里实际上探讨的是“注册服务”与面向对象之间的关系。

抽象

在设计并实现一个抽象之后,应尽量保持其稳定性,避免朝三暮四地频繁改变。所谓的“保持稳定”,既包括了抽象的外部表现,也应包括其内部实现。外部表现稳定意味着抽象所声明的方法、参数、返回值等应保持不变;而内部实现稳定则要求实现抽象的代码应尽量减少变更。就像汽车减震器一样:在轮胎颠簸时,保持车身基本稳定。

汽车减震器

注册器模式的核心在于注册服务。显然,注册模式与注册器的外部表现的稳定性无关,其主要作用是维持注册器内部实现的稳定性。

如果没有注册服务,注册器模式将退化为工厂模式。普通的工厂模式最大的问题在于内部实现不稳定。无论是增加新的服务实例还是添加新的工厂实例,都需要修改工厂的内部实现,以便将新实例添加到工厂中。下面的代码就是不使用注册模式的例子:

public class FactoryImpl implements Factory{    @Resource(name="serviceA")    public Service serviceA;    @Resource(name="serviceB")    public Service serviceC;    /** 新增服务实例后,我们需要把它保存到工厂中 */    @Resource(name="serviceNew")    public Service serviceNew;
public Service getService(TicketEnum t){ switch(t){ case A: return serviceA; case B: return serviceB; case N: /** 同时,需要添加新服务的取用方式 */ return serviceNew; default: return null; } }}

使用注册器模式之后,上述代码可以改写为:

public class FactoryImpl implements Factory{    private Map<TicketEnum, Service> serviceMap;
/** 通过构造器,注入所有注册器,并构建注册器服务 */ @Autowired pubilc FactoryImpl(ServiceRegistry registeies){ serviceMap = registeies.stream().collect(Collectors.toMap(s->s.registryTo(),s->s)); }
public Service getService(TicketEnum t){ return serviceMap.get(t); }}
public interface ServiceRegistry extends Service{ TicketEnum registryTo();}@Servicepublic class ServiceA implements ServiceRegistry{ public TicketEnum registryTo(){return TicketEnum.A;}}@Servicepublic class ServiceB implements ServiceRegistry{ public TicketEnum registryTo(){return TicketEnum.B;}}/** 使用注册器模式后,只需要新增服务实例即可,无需改动注册器和工厂。*/@Servicepublic class ServiceNew implements ServiceRegistry{ public TicketEnum registryTo(){return TicketEnum.N;}}
在修改后的代码中,只要服务注册的规则不变(如一个TicketEnum对应一个服务),那么,无论新增多少个服务实例,都不需要修改注册器。这样做,不仅能提升注册器的稳定性,而且提高了注册器的可扩展性:即使把它封装在不可变的Jar包内,我们依然可以借助它的注册机制来扩展新的服务。

高内聚低耦合

严格来说,注册器模式实际上并不具备高内聚低耦合的特点。

注册器模式的核心是注册服务,而注册服务的核心是什么呢?是如何保存服务实例以及如何获取服务实例。如果在取用服务中,要按照ticket-service的关系来获取实例;那么在保存服务中,就也要按照这个关系来保存服务实例;在注册服务中,自然也要按照这个关系来注册服务实例。如果取用服务的逻辑或数据关系发生了变更,那么保存服务和注册服务也必须相应地进行修改。

我们曾经遇到过这个问题。最初设计中,每个产品下的借据都有一套自己的记账服务。因此,只需将记账服务注册到产品代码上,就可以由工厂统一对外提供服务。这部分代码与前面的TicketEnum-ServiceRegistry类似,这里就不再赘述。

随着业务的发展,我们的产品引入了担保模式。这样一来,借据就有了两套记账服务:有担保记账和无担保记账。记账服务与产品代码的关系也就从1:1变成了1:[1..2],无法简单地注册到产品代码上了。为了修改这段代码,我们可费了大劲了。

这个问题的原因,就是注册器模式的“低内聚”、“高耦合”。

如果不使用注册器模式,取用、保存的内在逻辑都在工厂内部。此时,工厂自身高度内聚,它与服务实例之间的耦合度也较低。然而,使用了注册器模式之后,这些逻辑都随着注册服务而分散到了服务实例中:服务实例们要根据工厂的取用逻辑,将自己注册进来。这就降低了工厂的内聚性,而提高了工厂与服务实例之间的耦合性。

这让我想起了初次组织敏捷开发时的趣事。按照敏捷的要求,将故事拆分为任务后,团队成员应该自己选择负责的任务。我头几次提出这个要求时,组员们都面面相觑,然后笑着说:“还是你来分派吧。”组长统一给组员分配任务的方式就像工厂模式统一给服务实例安排调用方一样。而敏捷模式中的组员主动认领任务则与注册器模式中的服务实例主动注册机制异曲同工。

传统团队与敏捷团队

敏捷模式的开发效率比瀑布模式更高。但也不可否认,敏捷把一些原本只属于组长的职责交给了每一个组员。同样的,注册器模式也把一些原本只属于工厂的职责交给了每一个服务实例。这些职责一交出去,注册器模式的“高内聚低耦合”就成梦幻泡影了。

尽管不够高内聚低耦合,注册器模式居然也有易扩展的优点。至少在新增服务实例时,注册器模式下只需编写新服务实例的代码,在工厂模式下还要再修改工厂的代码。多改一处代码,可不止会增加一份开发工作量,还会增加若干份测试回归工作量,对整个项目的影响不可小觑。


封装继承多态


封装

提到注册器模式不够高内聚低耦合时,相信已经有人想到:这是因为它封装性不够的缘故。注册器模式把工厂模式的数据结构或存取逻辑分散到服务实例中,本质上就是注册器模式打破了工厂模式的封装,把它私有的数据或行为变为了公有。由此可以说,与高内聚低耦合一样,注册器模式在封装方面做得也不够好。

继承和多态

注册器模式与继承和多态都没有太大关系:它自己并没有用到继承。不过,为了注册到注册器上,服务实例通常都会继承或者实现一个通用的“注册类”。这里,服务实例和“注册类”之间会用到继承和多态。


5+1设计原则


单一职责原则

作为一个“给工厂模式增加一个新功能”后得到的新模式,注册器模式与“单一职责原则”基本无缘。它既要承担工厂模式的取用、保存职责,又要承担新增功能带来的注册职责。这样做的结果是显而易见的:无论我们要变更取用、保存还是注册功能,都不得不修改注册器模式的代码。单一职责原则可不是这样说的。

注册器模式对单一职责原则的破坏还不止于此。

让我们把目光放到服务实例上来。原本,它只需要承担自己的业务职责,只有当业务逻辑出现变更时才会发生改变。然而,在注册器模式下,服务实例竟也背上了“注册”职责:它还得负责把自己注册到注册器中去。如果注册逻辑变了——比如从按“产品代码”注册改为按“产品代码加担保模式”注册,服务实例们要不要跟着改呢?如果没有点“奇技淫巧”,答案几乎一定是“要改”。

例如,在“高内聚低耦合”章节提到过的,产品增加担保模式后注册器模式发生改变的问题,原因就是它违反了单一职责原则。我们可以来看看代码。

刚开始,这个功能中的注册器是这样的:

@Servicepublic BizFactoryImpl implements BizFactory{    /** 保存服务:按产品代码:服务实例=1:1的关系保存数据 */    private Map<String, Biz> map;    /** 注册服务:按保存和取用逻辑,注册所需服务 */    public BizFactoryImpl(Collection<BizRegistry> regs){        map = regs.stream()                .collect(Collctors.toMap(r->r.regToProduct(), r->r));    }    /** 取用服务:根据产品代码获取对应服务实例 */    public Biz fetch(Data data){        return map.get(data.getProductCode());    }    }/** 注册接口 */public interface BizRegistry extends Biz{    /** 把当前服务实例注册到这个产品代码上 */    String regToProduct();}/** 服务实例A,同时承担了业务职责和注册职责 */public class BizA implements BizRegistry{    /** 业务服务,略 */    public void doService(Data data){    }    /** 注册到产品A上 */    public String regToProduct(){        return "ProductA";        }}

随着业务发展,产品和服务实例之间的关系也从1:1变成了1:2——同一个产品会对应有担保模式和无担保模式这两类服务。这样一来,这个注册器从取用到注册,以及所有服务实例,全都要跟着改:

@Servicepublic BizFactoryImpl implements BizFactory{    /** 保存服务:按(产品代码+担保模式):服务实例=1:1的关系保存数据 */    private Map<String, Biz> map;    /** 注册服务:按保存和取用逻辑,注册所需服务 */    public BizFactoryImpl(Collection<BizRegistry> regs){        map = regs.stream()                .collect(Collctors.toMap(                    // 改动1:按产品代码加担保模式注册。                    // 这里把二级结构“拉平”为一级结构                    r->r.regToProduct()+":"+r.regToGuarentte(),                    r->r));    }    /** 改动2:      * 取用服务:根据产品代码加担保模式获取对应服务实例 */    public Biz fetch(Data data){        return map.get(data.getProductCode()+":"+data.getGuarentte());    }    }/** 注册接口 */public interface BizRegistry extends Biz{    /** 把当前服务实例注册到这个产品代码上 */    String regToProduct();    /** 改动3:增加注册方式      * 把当前服务实例注册到这个担保模式上 */    String regToGuarentte();}/** 服务实例A,同时承担了业务职责和注册职责 */public class BizA implements BizRegistry{    /** 业务服务,略 */    public void doService(Data data){    }    /** 注册到产品A上 */    public String regToProduct(){        return "ProductA";        }    /** 改动4:所有服务实例都增加注册方式      * 注册到有担保模式上 */    public String regToGuarentte(){        return "Guaranteed";        }}

从“改动1”到“改动3”,还算可以接受。可是其中的“改动4”,改动范围之大简直令人发指。要不是用了点“小技巧”,光着一项就够我们忙到第二天了。至于是什么“小技巧”,大家不妨自己想想哈哈哈。


开闭原则

虽然注册器模式不够高内聚低耦合、封装不好、也不遵守单一职责原则,奇妙的是,它非常符合开闭原则的要求。只需想想在注册器模式下,新增一个服务实例都需要修改哪些代码,就能够清楚地看出它的这一优点。

反正不管改多少代码,我都只用静静地看着你

写到这里,我不禁对注册器模式产生了好奇:它到底能违反多少面向对象设计原则、又能满足多少设计要求呢?它为什么会违反这么多原则?又是如何做到遵守这么多要求的?这是“故意的”还是“不小心的”?在使用时,我们又该如何取舍呢?

权且把它们记下来、放到一边。先看看注册器模式与其它几个设计原则之间的关系,然后再来讨论这些问题吧。


里氏替换原则

注册器模式与继承、多态之间的关系主要体现在服务实例上;它与里氏替换原则的关系也是如此。大多数时候,只有服务实例会用到继承和多态,因而,也只有服务实例需要考虑里氏替换原则。

对于注册器模式中的服务实例来说,里氏替换原则有时会有意想不到的作用。我们来看这段代码:

public class FactoryImpl implements Factory{    private Map<TicketEnum, Service> serviceMap;
/** 通过构造器,注入所有注册器,并构建注册器服务 */ @Autowired pubilc FactoryImpl(ServiceRegistry registeies){ serviceMap = registeies.stream().collect(Collectors.toMap(s->s.registryTo(),s->s)); }
public Service getService(TicketEnum t){ return serviceMap.get(t); }}
public interface Service{ void doService();}public interface ServiceRegistry extends Service{ TicketEnum registryTo();}@Servicepublic class ServiceA implements ServiceRegistry{ public TicketEnum registryTo(){return TicketEnum.A;} public void doService(){ // 执行服务A的业务逻辑:三个内部方法 step1(); step2(); step2(); }}
在上面这段代码的基础上,我们通过继承ServiceA来扩展一个新的服务实例:
@Servicepublic class ServiceA1 extends ServiceA{    protected void step2(){        // 子类只需要修改第二步即可。    }}

不得不说,ServiceA1倒是很符合单一职责原则。它专注于自己的特定逻辑,只重写了父类的step2()方法,而无需关心其它逻辑。但是,如果只关注自己的业务职责、忽略了注册职责,这里反而会遇到问题。在这个例子中,ServiceA1会和父类ServiceA就会同时注册到TicketEnum.A上,然后抛出异常。

这是一种fast-fail机制,虽然系统服务无法启动,至少不会造成更大的业务问题。万一没有这类“防御式编程”逻辑,系统默默地做了错误处理,后果可就严重了。

怎么预防呢?我们可以从里氏替换原则的角度来处理。

由于子类ServiceA1重写了父类的方法实现step2(),它其实违反了里氏替换原则。按照里氏替换原则的要求,我们可以把它们改写成这样:

/** 抽取一个父类 */public abstract BaseServceA implements ServiceRegistry{    public doService(){        // 执行服务A的业务逻辑:三个内部方法        step1();        step2();        step2();    }    // 以下两个方法必须留给子类实现    public abstract TicketEnum registryTo();    protected abstract void step2();    // 其它方法父类实现,略}
// 继承基类,重写空方法;不重写父类已有实现@Servicepublic class ServiceA extends BaseServceA{ public TicketEnum registryTo(){return TicketEnum.A;} public void step2(){ // 执行服务A的业务逻辑 }}// 继承基类,重写空方法;不重写父类已有实现@Servicepublic class ServiceA1 extends BaseServceA{ public TicketEnum registryTo(){return TicketEnum.A1;} protected step2(){ // 执行服务A1的业务逻辑 }}

改写之后,由于registryTo()和step2()方法都要交给子类实现,因此父类中并不提供方法体,只保留一个抽象的方法声明。子类必须自己实现父类中的抽象方法,也就避免了按默认的、但是错误的方式注册到注册器中这一问题。


接口隔离原则

注册器模式违反了单一职责原则。在接口隔离原则方面,它可以扳回一城,也算亡羊补牢了。

通常情况下,服务实例都会有自己的业务接口。这个接口会定义业务所需的方法、入参和返回值。

对注册器模式而言,仅仅知道业务接口还不够,还需要知道业务接口与取用逻辑之间的关联。有时,这种关联是一种数据映射关系;有时则是一段判断逻辑。

尽管这种关联关系最终也要由服务实例来实现,但它毕竟与业务服务无关,不应该放在业务接口中,以免暴露给业务调用方。因此,注册器模式通常会增加一个注册接口,用来声明业务服务和取用逻辑之间的关联关系。就像这样:

/** 业务服务接口 */public interface Service{    void doService();}/** 注册接口。  * 这个接口定义的关联关系是:一个Service(即当前实例)  * 注册到一个TicketEnum(由registryTo()方法指定)上。  */public interface ServiceRegistry extends Service{    TicketEnum registryTo();}

如果注册器模式使用了SpringIOC等框架的自动装配功能,不做接口隔离有时可能遇到问题。我在使用SpringSecurity权限框架时就曾遭遇过,详情参见:SpringBoot+SpringSecurity误拦截静态资源问题调研

总之,虽然注册器模式违反了单一职责原则,但它可以借助接口隔离原则做一些区隔,为代码实现提供一些便利。


依赖倒置原则

依赖倒置原则要求系统的高层不要直接依赖底层,而要让二者都依赖一个中间层。

在注册器模式中,我们可以把“服务调用方”代入“高层”,把“服务实例”代入“底层”,把“注册器”代入“中间层”。这样,我们就可以直截了当地说:注册器模式非常符合依赖倒置原则。

不止如此。结合接口隔离原则一节中提到的“注册接口”,我们还可以在注册器内部发现依赖倒置原则的痕迹。在这里,我们需要把“注册器”代入“高层”,把“服务实例”仍代入“底层”,此时的“中间层”就是前面提到过的“注册接口”。它们之间的类结构如下图所示:

类结构与分层

从上图中我们可以清晰地看到打工人永远在底层注册器模式与依赖倒置原则之间的亲密关系:从里子到面子,注册器模式都非常的“依赖倒置”。


迪米特法则

和单一职责原则中的问题一样:注册器模式没有很好地遵守迪米特法则。

迪米特法则又叫“最小知识法则”,它要求每个类都应该只包含最必要的知识。对注册器模式中的服务实例来说,“怎样注册”是它的必要知识吗?我认为不算。没有这部分知识,它们同样可以提供服务。只是为了分担注册器的功能,它们才跳出舒适圈,把额外的工作职责和相关的知识扛到了自己的肩上。

是跳出舒适圈,还是扩展舒适圈,请自己选择

写到这里,我们可以小结一下:注册器模式不够高内聚、低耦合;在封装、单一职责原则和迪米特法则方面做得很差;继承、多态、里氏替换原则和接口隔离原则不是它的充分或必要条件,但可以为它增色;只在依赖倒置原则和开闭原则方面,注册器模式表现颇为亮眼。

这么一看,注册器模式的优点屈指可数,而缺点简直罄竹难书。这样一个设计模式,真有存在和使用的必要吗?

不可否认,注册器模式有很多问题。但同时应该注意到,注册器模式的作用范围是抽象内部的实现,而非抽象对外的表现。它所关注的服务实例作为内部实现,通常都是可控的。在可控范围内传播耦合、职责、知识,风险也同样可控。从这个角度来看,注册器模式的这些缺点其实都还可以接受。

有人把架构设计定义为决策,有人则把它定义为取舍。无论是决策还是取舍,注册器模式都可以给我们一点启发:面对可控风险,我们可以选择接受它,而不一定非要消除、降低或者转移风险。毕竟,在可控环境下,连核聚变都可以搞上一搞,何况区区注册器模式呢?

其它设计模式

策略模式

前文中给出了这张图:

类结构与分层

这张图很清晰地表达了注册器模式对策略模式的依赖:若干个服务实例,既是注册接口下的策略实现类,也是服务接口下的策略实现类。

注册器不可能直接面向服务实例的实现类。不然的话,有多少个服务实例,注册器就要提供多少个注册方法。更要命的在于,每增加一个服务实例,注册器就要增加一个注册方法。这与注册器模式的初衷背道而驰。

/** * 如果直接面向服务实例提供注册服务 * 注册器就会变成这幅鬼样子 */public class Registry{    public void register(ServiceAImpl serviceA){        // 实现略        }    public void register(ServiceBImpl serviceB){        // 实现略        }    public void register(ServiceCImpl serviceC){        // 实现略        }    public void register(ServiceDImpl serviceD){        // 实现略        }}public class ServiceAImpl {}public class ServiceBImpl {}public class ServiceCImpl {}public class ServiceDImpl {}

实践中,我们一定会为服务实例提供一个注册接口。注册器面向这个接口提供服务:

/** * 注册器面向注册接口提供服务 * 接口方法声明就“一劳永逸”了。 */public class Registry{    public void register(Register service){        // 实现略        }}public interface Register{    // 声明略}public class ServiceAImpl implement Register{}public class ServiceBImpl implement Register{}public class ServiceCImpl implement Register{}public class ServiceDImpl implement Register{}public class ServiceEImpl implement Register{    // 后面再增加新的服务实例,也不用修改注册器}
如果没有策略模式,注册器模式根本玩不转。如果没有注册器模式呢?

如果没有注册器模式,策略实现类们只怕要谢天谢地了:它们不再需要考虑注册功能,专注于提供业务服务即可。这样的福气,给谁谁不要呢?


工厂模式

这样的福气,工厂模式就不想要。

如果没有注册器,工厂模式就要自己“收集”服务实例。有了注册器,工厂模式化身为注册器模式,“收集”功能转化为“注册”功能,分散到各个服务实例中,不再由工厂模式承担。

可见,加上注册功能,工厂模式就会“进化”为注册器模式。这也就是前文所说:如果削去注册服务、只保留保存服务和取用服务的话,注册器模式就会退化为工厂模式。

人使用工具则进化;人依附工具则退化。

这也就是注册器模式和工厂模式之间的关系。

复合模式

在讨论工厂模式时,我们提出了这样一个复合模式:

使用工厂模式的复合模式

这一复合模式的缺点,主要体现在两个方面。

最常见的问题是:当我们扩展新业务时,除了要增加一个服务实现类之外,势必还要修改工厂类的代码实现。这不仅不符合开闭原则,而且可能出现增加了实现类却忘记修改工厂类的问题。毕竟,从我们copy的服务实现类当中,完全看不到工厂类的蛛丝马迹。

还有一个特殊情况:如果工厂的代码不在业务项目中,我们甚至无法修改它的代码,更不能向其中新增实现类。这是我在写业务框架时真实遇到过的问题,虽然情况特殊,然而一旦遇上就让人焦头烂额。

不难看出,把工厂模式升级为注册器模式之后,这两个问题就迎刃而解了。

使用注册器模式的复合模式

这里,考虑到有可能无法修改工厂类的代码,我把注册器类设计为继承工厂类,以此来扩展新功能。

众所周知,使用继承不如使用组合。一般来说,所有类的继承(非接口实现)都可以转换为组合,即使是基于继承的模板模式也是如此。所以,尽管这个承转有点硬,下一次我们就来聊聊模板模式。

往期索引

《面向对象是什么》

从具体的语言和实现中抽离出来,面向对象思想究竟是什么?
公众号:景昕的花园面向对象是什么

抽象

抽象这个东西,说起来很抽象,其实很简单。

花园的景昕,公众号:景昕的花园抽象

高内聚与低耦合

细说几种内聚

细说几种耦合

"高内聚"与"低耦合"是软件设计和开发中经常出现的一对概念。它们既是做好设计的途径,也是评价设计好坏的标准。

花园的景昕,公众号:景昕的花园高内聚与低耦合

封装

继承

多态》

——“面向对象的三大特性是什么?”
——“封装、继承、多态。”

《[5+1]单一职责原则》
单一职责原则非常好理解:一个类应当只承担一种职责。因为只承担一种职责,所以,一个类应该只有一个发生变化的原因。
花园的景昕,公众号:景昕的花园[5+1]单一职责原则


《[5+1]开闭原则(一)

《[5+1]开闭原则(二)

什么是扩展?就Java而言,实现接口(implements SomeInterface)、继承父类(extends SuperClass),甚至重载方法(Overload),都可以称作是“扩展”。
什么是修改?在Java中,严格来说,凡是会导致一个类重新编译、生成不同的class文件的操作,都是对这个类做的修改。实践中我们会放宽一点,只有改变了业务逻辑的修改,才会归入开闭原则所说的“修改”之中。
花园的景昕,公众号:景昕的花园[5+1]开闭原则(一)

《[5+1]里氏替换原则(一)

《[5+1]里氏替换原则(二)

里氏替换原则(Liskov Substitution principle)是一条针对对象继承关系提出的设计原则。它以芭芭拉·利斯科夫(Barbara Liskov)的名字命名。1987年,芭芭拉在一次名为“数据的抽象与层次”的演讲中首次提出这条原则;1994年,芭芭拉与另一位女性计算机科学家周以真(Jeannette Marie Wing)合作发表论文,正式提出了这条面向对象设计原则

花园的景昕,公众号:景昕的花园[5+1]里氏替换原则(一)

[5+1]接口隔离原则(一)

[5+1]接口隔离原则(二)

一般我们会说,接口隔离原则是指:把庞大而臃肿的接口拆分成更小、更具体的接口。
不过,这并不是接口隔离原则的定义。实际上,接口隔离原则的定义其实是这样的……客户端不应被迫依赖它们压根用不上的接口;或者反过来说,客户端应该只依赖它们要用的接口。

花园的景昕,公众号:景昕的花园[5+1]接口隔离原则(一)

[5+1]依赖倒置原则(一)

[5+1]依赖倒置原则(二)

在Java世界里谈到依赖倒置原则,相信90%的人都会立即想起SpringIOC;还有9%的人会想起“面向接口编程”。最多只有1%的人能想起依赖倒置原则的真正定义
花园的景昕,公众号:景昕的花园[5+1]依赖倒置原则(一)

[5+1]迪米特法则(一)
[5+1]迪米特法则(二)

迪米特法则可以用一句话概括:Only talk to your friends。

 “只和你的朋友说话”,这是1987年的表述。2003/2004年左右,Karl Liebertherr对迪米特法则做了一次升级:由“Only talk to your friends”升级为了“Only talk to your friends who share your concerns”——“只和与你同忧同乐的朋友说话”。

花园的景昕,公众号:景昕的花园[5+1]迪米特法则

《常用设计模式-策略模式》

策略模式把属于同一类别的不同行为封装为某种“策略抽象”,而把这些行为统一为这个抽象下的某个“策略实现”。这样,我们就可以很灵活地决定在哪种场景下使用哪种“策略”了。

花园的景昕,公众号:景昕的花园常用设计模式-策略模式

《常用设计模式-工厂模式(一)》

《常用设计模式-工厂模式(二)》

工厂模式的定义其实很简单:提供一个独立组件,用以根据不同条件选择并构建不同实例。这个组件就是“工厂”。

花园的景昕,公众号:景昕的花园常用设计模式——工厂模式(一)


《常用设计模式-注册器模式(一)》

注册器模式提供了一个注册器,一组相同类型的实例可以被注册到注册器上并由后者进行保存。调用方则可以通过注册器来取用这些实例。
花园的景昕,公众号:景昕的花园常用设计模式-注册器模式(一)

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多