4.2 桥接模式(4.2)

目录

多次决策而非多继承

小模板方法和IStyle

参数化


使用多重策略,有一种基本场景:一个大的策略由多个小的策略步骤组合得到。例如一个投资策略,要考虑投资方向(房地产、实业等),再考虑投资地点,再考虑投资金额。一个最后的投资策略结果,是3个小的选择的数学组合。类似的例子比比皆是。

多次决策而非多继承

【实验6:一系列决策】假设类Client要选择一个代表人物/Person演讲/lecture()。Person的筛选的条件,包括职业/Profession,例如从教师、医生、律师……中选择了教师;地域/Region,例如从北京、上海、武汉……中选择了武汉;年龄段/Ages,例如从中年、青年、少年……中选择了青年。

在考虑设计方案的时候,有人将目光聚焦到类型层面,认为Profession、Region和Ages分别描述了人/Person的一个方面,或者说不同的变化维度。在类型层面上,Person通过多继承机制将成为Profession、Region和Ages的子类型,是一种不现实的实现方式,因为多继承的方式处理,将出现“武汉-年轻-教师”这样的随意组合的难以计数的类型,事实上,类型爆炸是显而易见的。关注类型层面没有抓住问题的关键,虽然当作引子来讨论桥接模式没什么大问题,但容易将人的关注点带歪,他们忽略了真正应该关注的是多次策略选择——函数级别。Client需要的是某个人完成lecture(),其代码要反映出不同的选项。能否如下体现多次策略选择呢?

foo(){
       p. lecture (); // Profession的子类的代码
       r. say (); // Region的子类的代码
       a. talk (); // Ages的子类的代码
}

不可。这是模板方法模式的应用方式,模板方法模式中各个抽象函数彼此独立。而演讲时一个函数如r. say ()应该成为另一个函数a. talk ()的一部分。可以用伪代码表示为:

p. lecture (string , r. say ( a. talk () ))

下面讨论实验6的实现。例程并不需要出现Person这个类(它让人联想到多继承),而Client只需要依赖Profession、Region和Ages三者之一。这三者的依赖关系可以是无序的,例程4-8中随意地采取了一种依赖关系Profession→Region→Ages。

在Profession→Region的实现中,可以使得Profession有一个成员变量Region,也可以将后者作为Profession的一个函数的参数。

在使用spring/God等依赖注入的场合,成员变量比较方便,如

public interface Profession{

    //下一次的策略,Region由配置文件任意选择

    Region next = (Region)God.create("Region"); // import yqj2065.util.God;

    abstract public String lecture(String s);//必须将自己的特色融入到Region. say ()中

}

在[4.3.3参数化]中考虑Region作为Profession的一个函数的参数。

虽然Profession、Region和Ages都可以有很多的子类型,简单起见,例程中和配置文件中仅仅使用图4-4提供的3个具体类如Teacher等(因为后面将通过lambda来创建它们的对象)。

此时,Client仅仅依赖一个接口Profession,Profession表现为一个大策略选择——找到了一个代表人物。该大策略如何组合而成,或者说如何实现,Client不感兴趣。对于Client而言,大策略的选择,就是选择了Profession的子类型如Teacher,而Teacher将自己的特色,融入到Region选项的代码中。换言之,Region的子类型的代码中,都拥有Profession的代码。

package chap4.bridgeP; //后面的类型中,包语句略
public interface Ages{
    public String talk(String s);
}
public class Youth implements Ages{
    public String talk(String s){
        String myStyle="带着青年的朝气,";
        return myStyle + s ;
    } 
}
import yqj2065.util.God;
public interface Region{
    Ages next = (Ages)God.create("Ages");
    public  String say(String s);
}
public class Wuhaner implements Region{     
    public String say(String s){
        return next.talk ("带着武汉的腔调,"+s);
    }
}
public interface Profession{
    Region next = (Region)God.create("Region");
    public String lecture(String s);;//必须将自己的特色融入到Region中
}

public class Teacher implements Profession{
    //String myStyle =this.getClass().getSimpleName();    
    @Override  public String lecture(String s){
        String myStyle = "带着粉笔灰的味道,说到:"+s;
        next.say(myStyle);        
    }
} 
public class Client{
    public static void main(String[] args){
        Profession p = (Profession)God.create("Profession");
        p.lecture("hello world");
    }
}

运行Client的输出为:

带着青年的朝气,带着武汉的腔调,带着粉笔灰的味道,说到:hello world

一个代表人物,如果是“中年-北京-律师”对象,将输出另外的模板式演讲。所谓模板式演讲,意味着两个“中年-北京-律师”对象的演讲肯定一样;不同的组合出来的对象,都按照固定的格式输出,如:Ages风格+ Region风格+Profession风格+说到:+内容。

可以通过代码更改输出的顺序。【如果你想初步理解桥接模式,可以到此为止

小模板方法和IStyle

如果编写多个Profession的子类型,就会发现Profession的各种子类型如医生,不仅要给出自己的风格,还要考虑与Region的融合。代码如下:

    @Override  public void lecture(String s){

        String myStyle = "带着消毒水的味道,说到:"+s;

        next.say(myStyle);       

    }

模板方法模式在面向对象领域的用法,就是将子类的公共代码——如与Region的融合的代码,从各种子类型中提取出来,作为父类型Profession的小模板方法。而子类型Teacher仅仅给出自己的风格。对例程4-8进行的改进,如例程4-9所示。同理,在Region和Ages类层次上也采用小模板方法。

package chap4.bridgeP;
import yqj2065.util.God;
public interface Profession{ 
    Region next = (Region)God.create("Region"); 
    //小模板方法
    public default String lecture(String s) {
        s = ",说到:"+s;
        String myStyle = this.style();//特意使用this
        return  next.say(myStyle +s);
    }
    public default String style() {  return "";}    // public abstract String style();
}
package chap4.bridgeP;
public class Teacher implements Profession{
    //代码向上集中后
    //String myStyle = "带着粉笔灰的味道"
    String myStyle =this.getClass().getSimpleName();    
    @Override  public String style(){         return myStyle;    }
}//Region和Ages类层次 略;Client代码不变

现在考虑一个有趣的问题。当Profession、Region和Ages都使用了小模板方法,而且各自的小模板方法所调用的抽象方法,在3个接口中都是style()时,一个意想不到的类型诞生了。在上一节中提到,设计时要避免某个类型如Person继承3个接口Profession、Region和Ages,而现在出现了3个接口的父类型IStyle。

在介绍桥接模式时,突兀的IStyle并没有什么作用。假设出现下面的代码,会有什么意味呢?

package chap4.bridgeP;
public interface IStyle{
    public default String style () {  return "";}
} 

public interface Profession extends IStyle{    
    IStyle base; //setter方法略

    //小模板方法
    public default String lecture(String s) {
        s = ",说到:"+s;
        String myStyle = this.style();//特意使用this
        return base.say(myStyle +s);
    }
}

不要诧异,装饰模式是特殊情况下的桥接模式

参数化

比较Profession有一个成员变量Region,将Region作为参数更体现桥接模式的本质,Profession的行为lecture(String s,Region next, Ages end)被Region和Ages参数化。

package chap4.bridgeP;
public interface Region /* extends IStyle*/ {    
    //参数化
    public default String say(String s,Ages next) {
         s = ","+s;
        String myStyle = this.style();//特意使用this
        return next.talk(myStyle +s);
    }
    public abstract String style(); 
}
public interface Profession {    
    public default String lecture(String s, Region next, Ages end) {
        s = ",说到:"+s;
        String myStyle = this.style();//特意使用this
        return next.say(myStyle + s,end );
    }.
    public abstract String style();    
}

package chap4.bridgeP;
import yqj2065.util.God;
public class Client{
    public static void main(String[] args){
        //p.lecture("hello world");
        
        //参数化之后
        Profession p = (Profession)God.create("Profession");
        Region next = (Region)God.create("Region");
        Ages end = (Ages)God.create("Ages");         
        p.lecture("hello world",next,end);
        ((Profession)()->"律师").lecture("hello world",()->"北京",()->"中年");       
    }
}

★桥接模式,将2次以上的策略选择串接起来形成大策略。

参数化更体现桥接模式的本质,Client需要一个教师-武汉-青年的组合人物的lecture(),而该方法包含了多个类层次的代码的融合。

[吐槽GoF]

[GoF]关于桥接模式意图的描述:"decouple anabstraction from its implementation so that the two can varyindependently".(将抽象与它的实现分离,使它们都可以独立地变化)。

这是一种较难理解的描述方式,“抽象与它的实现”是很少组队在一起的夫妻,通常使用的成对词汇是接口-实现,抽象类-实现类。

当依赖关系为Client→Profession→Region→Ages时,Client其实可以依赖Profession、Region和Ages三者中任何一个,例子中仅仅碰巧将Profession作为了“抽象”;而把Region作为“抽象”即Profession的lecture(String s)方法的实现的参与者(被GoF称为Profession的实现),Ages则递归地成为Profession的“实现的实现”。从封装的角度看,GoF的说法有一定的道理,就是说,把Profession、Region和Ages看成一个整体/大类型,而Client只看见Profession,可以将Profession视为抽象部分(不如称为接口),而Profession背后隐藏的类型可以称为实现部分。然而,Client关注的是多次策略选择,而非封装技巧

在参数化的代码中,Client依同时依赖Profession、Region 和Ages三者。而所谓“抽象”,不过是有方法将“实现”和“实现的实现”作为参数。换言之,Profession、Region和Ages分别描述了人不同的变化维度,通常谁作为“抽象”时随机的。

此外,所有被依赖的都是抽象类型。自然地,任何一次策略选择都可以独立地变化。

更加浓郁的函数式风格

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值