代码改变世界

设计模式之适配器模式和外观模式adapter and facade

2012-10-04 16:20  youxin  阅读(428)  评论(0编辑  收藏  举报

我们周围的适配器 

   交流的适配器。。。

这是真实世界的适配器,那面向对象的适配器又是什么?其实,oo适配器和真实世界的adapter扮演着同样的角色:将一个接口转换成另一个接口,以符合客户的期望。

  面向对象适配器

  假设已有一个软件系统,你希望它能和一个新的厂商类库搭配使用,但是这个新厂商设计出来的接口,不同于旧厂商的接口。

你不想改变现有的代码,解决这个问题(而且你也不能改变厂商的代码),所以该怎么办?可以写一个类,将新厂商的接口转换成你所期望的接口。

这个适配器工作起来就如同一个中间人,它将客户所发出的请求转换成厂商类能理解的请求。

还记得第1章中的鸭子吗?让我们看看鸭子接口和类的一个稍微简化的版本:

public interface Duck {
    public void quack();
    public void fly();
}

绿头鸭是鸭子的子类:

public class MallardDuck implements Duck{
    public void quack()
    {
        System.out.println("Quack");
    }
    public void fly()
    {
        System.out.println("I'm flying");
    }

}

 

下面是火鸡的接口:

public interface Turkey {
   public void gobble();
   public void fly();
}
public class WildTurkey implements Turkey{
 
    public void gobble() {
         System.out.println("Gobble gobble");
     
    }

    @Override
    public void fly() {
        System.out.println("I'm flying a shot distance");
    }
}

现在,假设你缺少鸭子对象,想用一些火鸡对象来冒充。显而易见,因为火鸡的接口不同,所以我们不能公然拿来用。

火鸡----》鸭子

那么,写个适配器把:

public class TurkeyAdapter implements Duck{
     Turkey turkey;
     public TurkeyAdapter(Turkey turkey){
         this.turkey=turkey;
     }
     public void quack(){
         turkey.gobble();
     }
    public void fly(){
        for(int i=0;i<5;i++){
            turkey.fly();
        }
    }
}

 

测试适配器:

public class DuckTestDrive {
    public static void main(String[] args) {
        MallardDuck duck = new MallardDuck();
 
        WildTurkey turkey = new WildTurkey();
        Duck turkeyAdapter = new TurkeyAdapter(turkey);
   
        System.out.println("The Turkey says...");
        turkey.gobble();
        turkey.fly();
 
        System.out.println("\nThe Duck says...");
        testDuck(duck);
  
        System.out.println("\nThe TurkeyAdapter says...");
        testDuck(turkeyAdapter);
    }
 
    static void testDuck(Duck duck) {
        duck.quack();
        duck.fly();
    }
}

 

适配器模式解析

客户是依据目标接口实现的。

过程如下:

1.客户通过目标接口调用适配器的方法对适配器发出请求

2适配器使用被适配器者把请求转换成被适配者的一个或多个调用接口

3.客户接收到调用的结果,但并未察觉这一切是适配器在起转换作用。

 

问:一个适配器只能够封装一个类吗?

答:适配器模式的工作是将一个接口转换成另一个。虽然大多数的适配器模式所采取的例子都是让一个适配器包装一个被适配者,但我们知道这个世界其实太复杂了,所以可能遇到这样一种情况,需要让一个适配器包装多个被适配者。这涉及另外一个模式,被称为外观模式

定义适配器模式

   适配器模式:将一个类的接口,转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以合作无间

看看他的类图:

adapter适配器与被适配者(adaptee)组合。

这个适配器模式充满良好的oo设计原则:使用对象组合,以修改的接口包装被适配者:这个做法还有额外的优点,那就是,被适配者的的任何子类,都可以搭配着适配器使用。

 

对象和类的适配器

  实际上有“2种”适配器:“对象”适配器和“类”适配器。前面的图是对象适配器的图。

 究竟什么是类适配器?为什么我们还没告诉你这种适配器?因为你需要多重 继承才能够实现它,这在java中是不可能的。但是当你在使用多重继承语言的时候,还是可能遇到这样的需求,让我们看看多重继承的类图:

唯一的差别就在于适配器继承了Target和Adaptee,而对象适配器利用组合的方法将请求传送给被适配者。java中类适配器可以使用继承和实现来实现它:参考:http://wenku.baidu.com/view/14607c270722192e4536f67d.html

所以:对象适配器和类适配器使用2种不同的适配方法(分别是组合和继承)。这2中实现的差异如何影响适配器的弹性?

适配器模式优点:

1、将目标类和适配者类解耦

2、增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性

3、灵活性和扩展性都非常好,符合开闭原则

类适配器还有的优点:

1、由于适配器类是适配者类的子类,因此可以再适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

类适配器的缺点:

1、对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为接口,不能为类,其使用有一定的局限性,不能将一个适配者类和他的子类同时适配到目标接口。

对象适配器还有的优点:

1、把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和他的子类都适配到目标接口。

对象适配器的缺点:

1、与类适配器模式相比,要想置换适配者类的方法就不容易。

 

在本章中,还有一个模式。

   我们现在要看一个改变接口的新模式,但是他改变接口的原因是为了简化接口。这个模式被巧妙地命名为外观模式(facade -pattern)。之所以这么称呼,是因为它将一个或数个类的复杂的一切都隐藏在背后,只显露出一个干净美好的外观

模式对比;

 模式                                       意图

装饰者                                    不改变接口,但加入责任

适配器                                   将一个接口转换成另一个接口

外观                                      让接口更简单。

 

甜蜜的家庭影院

  你组装了一套杀手级的系统,内含dvd播放器,投影机,自动屏幕,环绕立体声,等等。。,还有一大堆组件。

 如何观赏电影呢?

想看电影,必须执行一个个琐碎的小任务:

1.打开爆米花机

2.开始爆米花机

3将灯光调暗

4放下屏幕

5打开投影机

6

。。

还有一大堆的步骤要去做。让我们将这些任务写成类和方法调用。

但还不只是这样。。。。

1,看完电影后,你还要把一切全都关掉,怎么办?难道还要反向地把这些动作在进行一次?

2.如果要听cd或广播,难道也会这么麻烦?

怎么办?使用你的家庭影院竟如此复杂!让我们看看外观模式如何解决这团混乱,好让你能轻易地享受电影。。

 

 你需要的正是一个外观:有了外观模式,通过实现一个提供更合理的接口的外观类,你可以将一个复杂的子系统变得很容易。如果你需要复杂子系统的强大威力,别担心,还是可以使用原来的复杂接口的,但如何你需要的是一个方便使用的接口,那就使用外观。

 

构造家庭影院的外观:第一步是使用组合让外观能够访问子系统中所有的组件

public class HomeTheaterFacade {
    Amplifier amp;
    Tuner tuner;
    DvdPlayer dvd;
    CdPlayer cd;
    Projector projector;
    TheaterLights lights;
    Screen screen;
    PopcornPopper popper;
 
    public HomeTheaterFacade(Amplifier amp, 
                 Tuner tuner, 
                 DvdPlayer dvd, 
                 CdPlayer cd, 
                 Projector projector, 
                 Screen screen,
                 TheaterLights lights,
                 PopcornPopper popper) {
 
        this.amp = amp;
        this.tuner = tuner;
        this.dvd = dvd;
        this.cd = cd;
        this.projector = projector;
        this.screen = screen;
        this.lights = lights;
        this.popper = popper;
    }
 
    public void watchMovie(String movie) {
        System.out.println("Get ready to watch a movie...");
        popper.on();
        popper.pop();
        lights.dim(10);
        screen.down();
        projector.on();
        projector.wideScreenMode();
        amp.on();
        amp.setDvd(dvd);
        amp.setSurroundSound();
        amp.setVolume(5);
        dvd.on();
        dvd.play(movie);
    }
 
 
    public void endMovie() {
        System.out.println("Shutting movie theater down...");
        popper.off();
        lights.on();
        screen.up();
        projector.off();
        amp.off();
        dvd.stop();
        dvd.eject();
        dvd.off();
    }

    public void listenToCd(String cdTitle) {
        System.out.println("Get ready for an audiopile experence...");
        lights.on();
        amp.on();
        amp.setVolume(5);
        amp.setCd(cd);
        amp.setStereoSound();
        cd.on();
        cd.play(cdTitle);
    }

    public void endCd() {
        System.out.println("Shutting down CD...");
        amp.off();
        amp.setCd(cd);
        cd.eject();
        cd.off();
    }

    public void listenToRadio(double frequency) {
        System.out.println("Tuning in the airwaves...");
        tuner.on();
        tuner.setFrequency(frequency);
        amp.on();
        amp.setVolume(5);
        amp.setTuner(tuner);
    }

    public void endRadio() {
        System.out.println("Shutting down the tuner...");
        tuner.off();
        amp.off();
    }
}

上面的代码中:将子系统的组件整合成一个统一的接口。实现了一个简化的接口。

现在我们可以观赏电影,用最轻松的方式了!

public class HomeTheaterTestDrive {
    public static void main(String[] args) {
        Amplifier amp = new Amplifier("Top-O-Line Amplifier");
        Tuner tuner = new Tuner("Top-O-Line AM/FM Tuner", amp);
        DvdPlayer dvd = new DvdPlayer("Top-O-Line DVD Player", amp);
        CdPlayer cd = new CdPlayer("Top-O-Line CD Player", amp);
        Projector projector = new Projector("Top-O-Line Projector", dvd);
        TheaterLights lights = new TheaterLights("Theater Ceiling Lights");
        Screen screen = new Screen("Theater Screen");
        PopcornPopper popper = new PopcornPopper("Popcorn Popper");
 
        HomeTheaterFacade homeTheater = 
                new HomeTheaterFacade(amp, tuner, dvd, cd, 
                        projector, screen, lights, popper);
 
        homeTheater.watchMovie("Raiders of the Lost Ark");
        homeTheater.endMovie();
    }
}

我们很轻松地使用了简化的接口,先开启电影,然后关闭电影。

 

定义外观模式:

   想要使用外观模式,我们创建了一个接口简化而统一的类,用来包装子系统中一个或多个复杂的类

定义:

  外观模式:提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用

外观的意图是要提供一个简单的接口,好让一个子系统更易于使用。

 

最少知识原则 

 最少知识(Least Knowledge)原则告诉我们要减少对象之间的交互,只留下几个“密友”。这个原则通常是这么说道:

  最少知识原则:只和你的密友谈话。talk only to your  immediate friend.

这个原则希望我们在设计中,不要让太多的类耦合在一起,免得修改系统中一部分,会影响到其他部分。如果许多类之间相互依赖,那么这个系统就会变成一个易碎的系统,他需要花许多成本维护,也会因为太复杂而不容易被其他人了解。

这段代码耦合了多少类??

public float getTemp(){

           return station.getThermometer().getTemperature();

}违法了原则,调用的方法属于另一次调用的返回对象。

 

如何不用赢得太多的朋友和影响太多的对象

究竟要怎样才能避免这样呢?这个原则提供了一些方针:就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法:

1该对象本身

2.被当做方法的参数而传递进来的对象

3此方法所创建或实例化的任何对象

4对象的任何组件

前面的3个告诉我们,如果某对象是调用其他的方法的返回结果,不要调用该对象的方法。

最后一个:把“组件”想象成是被实例变量所引用的任何对象。换句话说,把这想象成“有一个has a”关系。

这听起来有点严厉,不是吗?如果调用从另一个调用中返回的对象的方法,会有什么害处呢?如果我们这样做,相当于向另一个对象的子部分发请求(而增加我们直接认识的对象数目)。在这种情况下,原则要我们改为要求该对象为我们做出请求,这么一来,我们就不需要认识该对象的组件了(让我们的朋友圈子维持在最小的状态)。比方说:

 

将方法调用保持在界限内

 这是一个汽车类,展示调用方法的各种做法,同时还能遵守最少知识原则

最少知识原则也叫做law of Demeter 迪米特法则。我们倾向于使用它有几个原因:

1.这个名字更直接

2.法则(law)给人的感觉是强制的。事实上,没有任何原则是法律(law0.所有的原则都应该在有帮助的时候才遵守。所有的设计都不免需要折中。

 

http://taswar.zeytinsoft.com/2009/04/03/law-of-demeter-principle-of-least-knowledge/