从封装变化的角度看设计模式接口隔离

封装变动之接口隔离

在组件的构建过程当中,某些接口之间间接的依赖经常会带来很多问题、甚至根本无法实现。采纳增加一层间接(稳固)的接口,来隔离原本相互严密关联的接口是一种常见的解决方案。

这里的接口隔离不同于接口隔离准则,接口隔离准则是对接口职责隔离,也就是尽量减少接口职责,使得一个类对另一个类的依赖应该建设在最小的接口上。

而这里所讲到的接口隔离是对依赖或者通信关系的隔离,通过在原有零碎中退出一个档次,使得整个零碎的依赖关系大大的升高。而这样的模式次要有外观模式、代理模式、中介者模式和适配器模式。

外观模式 – Facade

Facade模式其次要目标在于为子系统中的一组接口提供一个统一的界面(接口),Facade模式定义了一个高层接口,这个接口使得更加容易应用。

在咱们对系统进行钻研的时候,往往会采纳形象与合成的思路去简化零碎的复杂度,因而在这个过程当中就将一个简单的零碎划分成为若干个子系统。也正是因为如此,子系统之间的通信与相互依赖也就减少了,为了使得这种依赖达到最小,Facade模式正好能够解决这种问题。

Facade模式体现的更多的是一种接口隔离的思维,它体现在很多方面上,最常见的比如说用户图形界面、操作系统等。这都能够体现这样一个思维。

Facade模式从构造上能够简化为下面这样一种模式,但其模式并不固定,尤其是体现在其外部子系统的关系上,因为其外部的子系统关系必定是简单多样的,并且SubSystem不肯定是类或者对象,也有可能是一个模块,这里只是用类图来体现Facade模式与其子系统之间的关系。

从代码体现上来看,能够这样体现:

public class SubSystem1 {
    public void operation1(){
        //实现子系统1的性能
        ......
    }
}
public class SubSystem2 {
    public void operation2(){
        //实现子系统2的性能
        ......
    }
}
public class SubSystem3 {
    public void operation3(){
        //实现子系统3的性能
        ......
    }
}
public class SubSystem21 extends SubSystem2{
    //对子系统2的扩大
    ......
}
public class SubSystem22 extends SubSystem2 {
    //对子系统2的扩大
    ......
}

下面子系统外部各局部的一个体现,如何联合Facade来对外隔离它的零碎外部简单依赖呢?看上面:

public class Facade {
    private SubSystem1 subSystem1;
    private SubSystem2 subSystem2;
    private SubSystem3 subSystem3;
    public Facade(){
        subSystem1 = new SubSystem1();
        subSystem2 = new SubSystem21();
        subSystem3 = new SubSystem3();
    }
    public void useSystem1(){
        subSystem1.operation1();
    }
    public void useSystem2(){
        subSystem2.operation2();
    }
    public void useSystem3(){
        subSystem3.operation3();
    }
}

当然,这只是Facade模式的一种简略实现,可能在真正的实现零碎中,会有着更加简单的实现,比方各子系统之间可能存在依赖关系、又或者调用各子系统时须要传递参数等等,这些都会给Facade模式的实现带来很大的影响。

public class Client {
    public static void main(String[] args) {
        Facade facade = new Facade();
        facade.useSystem1();
        facade.useSystem2();
        facade.useSystem3();
    }
}

当存在Facade之后,客户对子系统的拜访就只须要面对Facade,而不须要再去了解各子系统之间的简单依赖关系。当然对于一般客户而言,应用Facade所提供的接口天然是足够的;对于更加高级的客户而言,Facade模式并未屏蔽高级客户对子系统的拜访,也就是说,如果有客户须要依据子系统定制本人的性能也是能够的。

对Facade的了解很简略,然而在具体应用时,又须要留神些什么呢?

  • 进一步地升高客户与子系统之间的耦合度。具体实现是,应用抽象类来实现Facade而通过它的具体子类来应答不同子系统的实现,并且能够满足客户依据要求本人定制Facade。

    除了应用子类的形式之外,通过其余的子系统来配置Facade也是一个办法,并且这种办法的灵活性更好。

  • 在层次化构造中,能够应用外观模式定义零碎中每一层的入口。方才咱们就提到过,SubSystem不肯定只示意一个类,它蕴含的可能是一些类,并且是一些具备协作关系的类,那么对于这些类,天然也是应用外观模式来为其定义一个对立的接口。
  • Facade模式本身也有毛病,尽管它缩小零碎的相互依赖,进步灵活性,进步了安全性;然而其自身就是不合乎开闭准则的,如果子系统发生变化或者客户需要变动,就会波及到Facade的批改,这种批改是很麻烦的,因为无论是通过扩大或是继承都可能无奈解决,只能以批改源码的形式。

代理模式 – Proxy

在Proxy模式中,咱们创立具备现有对象的代理对象,以便向外界提供性能接口。其目标在于为其余对象提供一种代理以管制对这个对象的拜访。

这是因为一个对象的创立和初始化可能会产生很大的开销,这也就意味着咱们能够在真正须要这个对象时再对其进行相应的创立和初始化。

比方在文件系统中对一个图片的拜访,当咱们以列表模式查看文件时,并不需要显示整个图片的信息,只有在选中图片的时候,才会显示其预览信息,再在双击之后可能才会真正打个这个图片,这时可能才须要从磁盘当中加载整个图片信息。

对图片代理的了解就如同下面的结构图一样,在文件栏中预览时,只是显示代理对象当中的fileName等信息,而代理对象当中的image信息只会在真正须要Image对象的时候才会建设实线指向的分割。

通过下面的例子,能够分明的看到代理模式在拜访对象时,引入了肯定水平的间接性,这种间接性依据不同的状况能够附加相应的具体解决。

比方,对于近程代理对象,能够暗藏一个对象不存在于不同地址空间的事实。对于虚代理对象,能够依据要求创建对象、加强对象性能等等。还有爱护代理对象,能够为对象的拜访减少权限管制。

这一系列的代理都体现了代理模式的高扩展性。但同时也会减少代理开销,因为在客户端和实在主题之间减少了代理对象,因而有些类型的代理模式可能会造成申请的处理速度变慢。并且实现代理模式须要额定的工作,有些代理模式的实现非常复杂。

对于下面的例子,能够用类图更加具体地论述。

在这样一个构造中,jpg图片与图片代理类独特实现了一个图片接口,并且在图片代理类中寄存了一个对于JpgImage的援用,这个援用在未有真正应用到时,是为null的,只有在须要应用时,才对其进行初始化。

//Subject(代理的指标接口)
public interface Image {
    public void show();
    public String getInfo();
}
//RealSubject(被代理的实体)
public class JpgImage implements Image {
    private String imageInfo;
    @Override
    public void show() {
        //显示残缺图片
        ......
    }

    @Override
    public String getInfo() {
        return imageInfo;
    }
    public Image loadImage(String fileName){
        //从磁盘当中加载图片信息
       // ......
        return new JpgImage();
    }
}
//Proxy(代理类)
public class ImageProxy implements Image {
    private String fileName;
    private Image image;
    @Override
    public void show() {
        if (image==null){
           image = loadImage(fileName);
        }
        image.show();
    }

    @Override
    public String getInfo() {
        if (image==null){
            return fileName;
        }else{
            return image.getInfo();
        }
    }

    public Image loadImage(String fileName){
        //从磁盘当中加载图片信息
        ......
        return new JpgImage();
    }
}
public class Client {
    public static void main(String[] args) {
       Image imageProxy = new ImageProxy();
       imageProxy.getInfo();
       imageProxy.show();
    }
}

在理论的应用过程上,客户就能够不再波及具体的类,而是能够只关注代理类。

代理模式的品种有很多,依据代理的实现模式不同,能够划分为:

  • 近程代理:为一个对象在不同的地址空间提供部分代表。
  • 虚代理:为须要创立开销很大的对象生成代理。(如下面的实例)
  • 爱护代理:管制对原始对象的拜访。爱护代理次要用于对象应该有不同的爱护权限时。
  • 智能指引:在拜访对象时执行一些附加的操作。

以上的代理都是动态代理的模式,为什么说是动态呢,这是因为在实现的过程中,它的类型都是当时预约好的,比方ImageProxy这个类,它就只能代理Image的子类。

与动态绝对的天然就产生了动静代理。动静代理中,最次要的两种形式就是基于JDK的动静代理和基于CGLIB的动静代理。这两种动静代理也是Spring框架中实现AOP(Aspect Oriented Programming)的两种动静代理形式。这里,就不深刻了,前面有机会再对动静代理做一个具体的解说。

中介者模式 – Mediator

中介者模式用一个中介对象来封装一系列的对象交互。中介者使各对象不须要显示地互相援用,从而使其耦合涣散,而且能够独立地扭转它们之间的交互。

中介者模式产生的一个重要起因就在于,面向对象设计激励将行为分页到各个对象中。而这种散布就可能会导致对象间有许多连贯,这些连贯就是导致系统复用和批改艰难的起因所在。

就比方一个机场调度的实现,在这个性能当中,各个航班就是Colleague,而塔台就是Mediator;如果没有塔台的协调,那么各个航班飞机的起降将只能由航班飞机之间造成一个多对多(一对多)的通信网来管制,这种管制必然是及其简单的;然而有了塔台的退出,整个零碎就简化了许多,所有的航班只须要和塔台进行通信,也只须要接管来自塔台的管制即可实现所有工作。这就使得多对多(一对多)的关系转化成了一对一的关系。

看到中介者模式类图的时候,有没有察觉如同和哪个模式有点类似,有没有点像观察者模式。

之所以如此类似的起因就是观察者模式和中介者模式都波及到了对象状态变动与状态告诉这两个过程。观察者模式当中,指标(Subject)的状态发生变化就会告诉其所有的(Observer);同样,在中介者模式当中,其相应的共事类(一群通过中介者相互协作的类)状态发生变化,就须要告诉中介者,再由中介者来解决状态信息并反馈给其余的共事类。

因而,中介者模式的实现办法之一就是应用观察者模式,将Mediator作为一个Observer,各个Colleague作为Subject,一旦Colleague状态发生变化就发送告诉给Mediator。Mediator作出响应并将状态扭转的后果流传给其余的Colleague。

另外还有一种形式,是在Mediator中定义一个非凡的接口,各个Colleague间接调用这个接口,并将本人作为参数传入,而后由这个接口来抉择将信息发送给谁。

//Mediator
public class ControlTower {
    private List<Flight> flights
                        = new ArrayList<>();
    public void addFlight(Flight flight){
        flights.add(flight);
    }
    public void removeFlight(Flight flight){
        flights.remove(flight);
    }
    public void control(Flight flight){
        //对航班进行起降管制
        ......
        //如果航班腾飞,则从flights移除
        //如果航班起飞,则退出到flights
    }
}
public class Flight {
    private ControlTower cTower;
    public void setcTower(ControlTower cTower) {
        this.cTower = cTower;
    }
    public void changed(){
        cTower.control(this);
    }
}
public class Flight1 extends Flight{
    public void takeOff(){
        //腾飞操作
        ......
    }
    public void land(){
        //起飞操作
        ......
    }
}
public class Flight2 extends Flight{
    //腾飞 起飞 操作
    ......
}
public class Flight3 extends Flight{
  //同样 腾飞 起飞 操作
    ......
}

那么客户怎么应用这样一个模式呢?看上面这样一个操作:

public class Client {
    public static void main(String[] args) {
        ControlTower controlTower = new ControlTower();
        //假如一个飞机入场要么是有跑道闲暇要么是另一个飞机腾飞
        Flight f1 = new Flight1();
        f1.setcTower(controlTower);
        //此时一号机起飞,
        //controlTower调用contorl管制飞机起降
        f1.changed(); 

        Flight f2 = new Flight2();
        f2.setcTower(controlTower);
        //此时二号机起飞,
        //controlTower调用contorl管制1号飞机腾飞,二号起飞
        f2.changed();
        
        .......
    }
}

中介者模式次要解决的是,如果零碎中对象之间存在比较复杂的援用关系,导致它们之间的依赖关系构造凌乱而且难以复用该对象,就能够应用中介者来简化依赖关系。然而这也可能会使得中介者会宏大,变得复杂难以保护,所以在应用中介者模式时,尽量是在放弃中介者稳固的状况下应用。

适配器模式 – Adapter

适配器的目标在于将一个类的接口转换成客户心愿的另外一个接口,从而使得本来因为接口不兼容而不能在一起工作的类能够在一起工作。

首先在应用适配器的时候,须要明确的是,适配器不是在具体设计时增加的,而是解决正在退役的我的项目的问题。为什么,因为适配器自身就存在一些问题,比方明明我想调用的是一个文件接口,后果传输进去的却是一张图片,如果零碎当中呈现太多这样的状况,那无异会使得零碎的利用变得极其艰难。

所以只有在零碎正在使用,并且重构艰难的状况下,才抉择应用适配器来适配接口。

而适配器模式又依据作用对象能够分为类适配器和对象适配器两种实现形式。

假如咱们当初曾经存在一个播放器,这个播放器只能播放mp3格局的音频。然而当初又呈现了一个新的播放器,这个播放器有两种播放格局mp4和wma。

也就是说,当初的状况能够用下图来进行形容:

这时候,为了左边的零碎Player 融入到左边中,就能够采纳适配器模式。

通过减少一个适配器,并将player作为适配器的一个属性,当传入具体的播放器时,就在newPlay()中调用player.play()

具体实现如下:

//Adaptee (适配者,要求将这个存在的接口适配成指标的接口)
public interface Player {
    public void play();
}
public class Mp3Player implements Player {
    @Override
    public void play() {
        System.out.println("播放mp3格局");
    }
}
//Target(适配指标,须要适配成那个指标的接口)
public interface NewPlayer {
    public void newPlay();
}
public class WmaNewPlayer implements NewPlayer {
    @Override
    public void newPlay() {
        System.out.println("播放wmas格局");
    }
}
public class Mp4NewPlayer implements NewPlayer {
    @Override
    public void newPlay() {
        System.out.println("播放mp4格局");
    }
}

接下来就是适配器的实现了。

//对象适配器
//首先在适配器中,减少一个适配者(Player)的援用
//而后应用适配者(Player)实现适配指标(NewPlayer)的接口
public class PlayerAdapter implements NewPlayer {
    private  Player player;
    public PlayerAdapter(Player player){
        this.player = player;
    }
    @Override
    public void newPlay() {
        player.play();
    }
}

而后整个零碎的调用变动为:

public class Client {
    public static void main(String[] args) {
        //播放mp4,wma的模式不变
        NewPlayer mp4Player = new Mp4NewPlayer();
        mp4Player.newPlay();
        NewPlayer wmaPlayer = new WmaNewPlayer();
        wmaPlayer.newPlay();

        //如果要播放mp3格局,能够应用适配器来进行
        Player adapter
            = new PlayerAdapter(new Mp3Player());
        adapter.newPlay();
    }
}

这样的一个适配过程可能存在一点不欠缺的中央,就在于,尽管对两都进行了适配,但调用形式不对立。为了对立调用过程,其实还能够做如下批改:

//对象适配器批改为
//首先在适配器中,减少适配者(newPlayer)和指标(player)的援用
//而后应用适配者(newPlayer)实现适配指标(Player)的接口
public class PlayerAdapter implements NewPlayer {
    private  NewPlayer newPlayer;
    private  Player player;

    public PlayerAdapter(NewPlayer newPlayer){
        this.newPlayer = newPlayer;
    }
    public PlayerAdapter(Player layer){
        this.player = player;
    }
    @Override
    public void newPlay() {
        if(player!=null){
            player.play();
        }else{
            newPlayer.newPlay();
        }
    }
}
//这样批改适配器之后,客户类的调用就变成了都通过适配器来进行
public class Client {
    public static void main(String[] args) {
       //播放mp3
        Player adapter1 
           = new PlayerAdapter(new Mp3Player());
        adapter1.newPlay();
        //播放mp4
        Player adapter2 
            = new PlayerAdapter(new Mp4NewPlayer());
        adapter2.newPlay();
        //播放wma
        Player adapter3 
            = new PlayerAdapter(new WmaNewPlayer());
        adapter3.newPlay();
    }
}

之前说了除了对象适配器之外,还有类适配器。而类适配器如果要实现就须要适配中的适配者是一个曾经实现的构造,如果没有实现还须要适配者本人实现,这种实现形式就导致其灵活性没有对象适配器那么高。

其类图就是下面这样一种模式,次要区别体现在适配器的实现,而其局部变动不大。

//类适配器
public class PlayerAdapter extends Mp3Player implements NewPlayer {
    @Override
    public void newPlay() {
        play();
    }
}
//客户调用过程就变动为:
public class Client {
    public static void main(String[] args) {
        //播放mp4,wma的模式不变
        NewPlayer mp4Player = new Mp4NewPlayer();
        mp4Player.newPlay();
        NewPlayer wmaPlayer = new WmaNewPlayer();
        wmaPlayer.newPlay();
        //如果要播放mp3格局,能够应用适配器来进行
        Player adapter = new PlayerAdapter();
        adapter.newPlay();
    }
}

然而如果Player存在不同子类,那显著应用对象适配器是更好的抉择。

当然也不是说类适配器就不肯定没有对象适配器之外的劣势。两者的应用有不同的衡量。

类适配器:

  • 用一个具体的Adapater类对Adaptee和Target进行匹配。后果是当咱们想要匹配一个类以及所有它的子类时,类适配器就不再实用。
  • 因为Adapter是Adaptee的子类,这就使得Adapter能够重定义Adaptee的局部行为。
  • 不须要再引入对象,不须要引入额定的援用就能够失去adaptee。

对象适配器:

  • 容许一个Adapter与多个Adaptee——即Adaptee自身以及它的所有子类同时工作,并且Adapter也能够一次给所有的Adaptee增加性能。
  • 想要重定义Adaptee的行为比拟艰难,但对于加强Adaptee的性能却很容易。如果要自定义Adaptee的行为,就只能生成Adaptee的子类来实现重定义。

最初,最近很多小伙伴找我要Linux学习路线图,于是我依据本人的教训,利用业余时间熬夜肝了一个月,整顿了一份电子书。无论你是面试还是自我晋升,置信都会对你有帮忙!

收费送给大家,只求大家金指给我点个赞!

电子书 | Linux开发学习路线图

也心愿有小伙伴能退出我,把这份电子书做得更完满!

有播种?心愿老铁们来个三连击,给更多的人看到这篇文章

举荐浏览:

  • 干货 | 程序员进阶架构师必备资源免费送
  • 神器 | 反对搜寻的资源网站

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理