适配器模式——翻译、DataAdapter

适配器模式(Adapter),将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

在软件开发中,也就是系统的数据和行为都正确,但接口不符时,应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况,比如在需要对早期代码复用一些功能等应用上很有实际价值。

在GoF 的设计模式中,对适配器模式讲了两种类型,类适配器模式和对象适配器模式,由于类适配器模式通过多重继承对一个接口与另一个接口进行匹配,而C#、VB.NET、JAVA等语言都不支持多重继承(C++支持),也就是一个类只有一个父类,所以这里主要讲的是对象适配器。

结构图

image-20210821110312532

何时使用适配器模式

在想使用一个已经存在的类,但如果它的接口,也就是它的方法和你的要求不相同时,就应该考虑用适配器模式。

两个类所做的事情相同或相似,但是具有不同的接口时要使用它。而且由于类都共享同一个接口,使得客户代码可以统一调用同一接口,这样更简单、更直接、更紧凑。

公司内部,类和方法的命名应该有规范,最好前期就设计好,然后如果真的接口不相同时,首先不应该考虑用适配器,而是应该考虑通过重构统一接口。

要在双方都不太容易修改的时候再使用适配器模式适配,而不是一有不同时就使用它。

设计之初就需要考虑用适配器模式的时候:比如公司设计一系统时考虑使用第三方开发组件,而这个组件的接口与自己的系统接口是不相同的,而我们也完全没有必要为了迎合它而改动自己的接口,此时尽管是在开发的设计阶段,也是可以考虑用适配器模式来解决接口不同的问题。

代码实现

Player

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class Player {
    String name;

    public Player() {
    }

    public Player(String name) {
        this.name = name;
    }

    void attack(){
        System.out.println(name + " 冲啊");
    };
    void defend(){
        System.out.println(name + " 防啊");
    };
}

ConcretePlayer

1
2
3
4
5
6
public class ST extends Player{

    public ST(String name) {
        super(name);
    }
}
1
2
3
4
5
6
public class CB extends Player{

    public CB(String name) {
        super(name);
    }
}

need adaper class

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class YAO{
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public void jingong() {
        System.out.println("yao 冲啊");
    }

    public void fangshou() {
        System.out.println("yao 防啊");
    }
}

Adapter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class Adapter extends Player{

    private YAO yao = new YAO();

    public Adapter(String name) {
       yao.setName(name);
    }

    @Override
    void attack() {
        yao.jingong();
    }

    @Override
    void defend() {
        yao.fangshou();
    }
}

client

1
2
3
4
5
6
7
8
public static void main(String[] args) {
    Player st = new ST("st");
    st.attack();
    Player cb = new CB("cb");
    cb.attack();
    Player yao = new Adapter("yao");
    yao.defend();
}

适配器模式的应用

**DataAdapter用作DataSet和数据源之间的适配器以便检索和保存数据。DataAdapter通过映射FilI(这更改了DataSet中的数据以便与数据源中的数据相匹配)和Update(这更改了数据源中的数据以便与DataSet中的数据相匹配)来提供这一适配器。**由于数据源可能是来自SQL Server,可能来自Oracle,也可能来自Access、DB2,这些数据在组织上可能有不同之处,但我们希望得到统一的DataSet(实质是XML 数据),此时用DataAdapter就是非常好的手段,我们不必关注不同数据库的数据细节,就可以灵活的使用数据。

桥接模式——手机软件统一

很多情况用继承会带来麻烦。比如,对象的继承关系是在编译时就定义好了,所以无法在运行时改变从父类继承的实现。子类的实现与它的父类有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。当需要复用子类时,如果继承下来的实现不适合解决新的问题则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性

合成/聚合复用原则

合成/聚合复用原则(CARP),尽量使用合成/聚合,尽量不要使用类继承。

合成(Composition,也有翻译成组合)和聚合(Aggregation)都是关联的特殊种类。**聚合表示一种弱的“拥有”关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分;合成则是一种强的“拥有”关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样。**比方说,大雁有两个翅膀,翅膀与大雁是部分和整体的关系,并且它们的生命周期是相同的,于是大雁和翅膀就是合成关系。而大雁是群居动物,所以每只大雁都是属于一个雁群,一个雁群可以有多只大雁,所以大雁和雁群是聚合关系。

image-20210910201643658

合成/聚合复用原则的好处是,优先使用对象的合成/聚合将有助于你保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物。

桥接模式

桥接模式(Bridge),将抽象部分与它的实现部分分离,使它们都可以独立地变化。

什么叫抽象与它的实现分离?这并不是说,让抽象类与其派生类分离,因为这没有任何意义。实现指的是抽象类和它的派生类用来实现自己的对象。举个栗子,“手机”可以按照品牌来分类,也可以按照功能来分类。

结构图

image-20210910202048891

image-20210910202222096

代码实现

Abstraction类

1
2
3
4
5
6
7
8
9
public abstract class Phone {
    Software software;

    public void setSoftware(Software software) {
        this.software = software;
    }

    abstract void run();
}

RefinedAbstraction类

1
2
3
4
5
6
7
public class IOS extends Phone{

    @Override
    void run() {
        super.software.run();
    }
}

Implementor类

1
2
3
public interface Software {
    void run();
}

ConcreteImplementorA和 ConcreteImplementorB等派生类

1
2
3
4
5
6
public class Excel implements Software{
    @Override
    public void run() {
        System.out.println("run excel");
    }
}
1
2
3
4
5
6
public class Word implements Software{
    @Override
    public void run() {
        System.out.println("run word");
    }
}

客户端实现

1
2
3
4
5
6
7
public static void main(String[] args) {
    IOS ios = new IOS();
    ios.setSoftware(new Excel());
    ios.run();
    ios.setSoftware(new Word());
    ios.run();
}

实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。

组合模式——总公司-分公司-部门

组合模式(Composite),将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

结构图

image-20210910143655894

代码实现

Comonent

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public abstract class Component {

    String name;

    public Component(String name) {
        this.name = name;
    }

    public abstract void add(Component component);

    public abstract void remove(Component component);

    public abstract void display(int depth);
}

Composite

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ConcreteComponent extends Component {

    List<Component> children = new ArrayList<>();

    public ConcreteComponent(String name) {
        super(name);
    }

    @Override
    public void add(Component component) {
        children.add(component);
    }

    @Override
    public void remove(Component component) {
        children.remove(component);
    }

    @Override
    public void display(int depth) {
        System.out.println(String.join("", Collections.nCopies(depth,"-")) + name);
        for (Component child : children) {
            child.display(depth + 2);
        }
    }
}

Leaf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class Leaf extends Component{

    public Leaf(String name) {
        super(name);
    }

    @Override
    public void add(Component component) {

    }

    @Override
    public void remove(Component component) {

    }

    @Override
    public void display(int depth) {
        System.out.println(String.join("", Collections.nCopies(depth,"-")) + name);
    }
}

Client

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
    ConcreteComponent a = new ConcreteComponent("a");
    ConcreteComponent aa = new ConcreteComponent("aa");
    Leaf ab = new Leaf("ab");
    a.add(aa);
    a.add(ab);

    ConcreteComponent ac = new ConcreteComponent("ac");
    a.add(ac);
    Leaf aca = new Leaf("aca");
    ac.add(aca);

    Leaf aaa = new Leaf("aaa");
    aa.add(aaa);

    a.display(1);

}

应用场景

当发现需求中是体现部分与整体层次的结构时,以及希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑用组合模式了。

自定义控件,也就是把一些基本的控件组合起来,通过编程写成一个定制的控件,比如用两个文本框和一个按钮就可以写一下自定义的登录框控件,实际上,所有的Web控件的基类都是System.Web.Ul.Control,而Control基类中就有Add和Remove方法,这就是典型的组合模式的应用。

组合模式定义了包含Leaf(人力资源部和财务部)这些基本对象ConcreteComponent(分公司、办事处)等组合对象的类层次结构。基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断地递归下去,客户代码中,任何用到基本对象的地方都可以使用组合对象了。

用户不用关心到底是处理一个叶节点还是处理一个组合组件,也就用不着为定义组合而写一些选择判断语句了。

组合模式让客户可以一致地使用组合结构和单个对象。

装饰模式——穿搭

装饰模式(Decorator),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。

结构图

image-20210818194144797

image-20210818194711656

装饰模式是利用SetComponent来对对象进行包装的。这样每个装饰对象的实现就和如何使用这个对象分离开了,每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链当中

如果只有一个ConcreteComponent类而没有抽象的Component类,那么 Decorator类可以是ConcreteComponent 的一个子类。同样道理,如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把 Decorator和ConcreteDecorator的责任合并成一个类。

装饰模式代码实现

image-20210818195326617

“Person”类(ConcreteComponent)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class Person {

    private String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    void show(){
        System.out.println(name + "穿衣服");
    };
}

服饰类(Decorator)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class Decorator extends Person{

    private Person component;

    public void setDecorator(Person component) {
        this.component = component;
    }

    @Override
    void show() {
        if (component != null)
            component.show();
    }
}

具体服饰类(ConcreteDecorator)

1
2
3
4
5
6
7
public class Clothes extends Decorator{
    @Override
    void show() {
        System.out.println("衣服");
        super.show();
    }
}
1
2
3
4
5
6
7
public class Pants extends Decorator{
    @Override
    void show() {
        System.out.println("裤子");
        super.show();
    }
}
1
2
3
4
5
6
7
public class Shoe extends Decorator{
    @Override
    void show() {
        System.out.println("鞋子");
        super.show();
    }
}

主函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class DecoratorTest {

    public static void main(String[] args) {

        Person person = new Person("bin");
        Clothes clothes = new Clothes();
        Pants pants = new Pants();
        Shoe shoe = new Shoe();
        clothes.setDecorator(person);
        pants.setDecorator(clothes);
        shoe.setDecorator(pants);
        shoe.show();

    }

}

打印输出

image-20210818202031974

装饰模式是为已有功能动态地添加更多功能的一种方式。

但到底什么时候用它呢?

起初的设计中,当系统需要新功能的时候,是向旧的类中添加新的代码。这些新加的代码通常装饰了原有类的核心职责或主要行为,但这种做法的问题在于,它们在主类中加入了新的字段,新的方法和新的逻辑,从而增加了主类的复杂度,而这些新加入的东西仅仅是为了满足一些只在某种特定情况下才会执行的特殊行为的需要。而装饰模式却提供了一个非常好的解决方案,它把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,因此,当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择地、按顺序地使用装饰功能包装对象了。

装饰模式的优点是把类中的装饰功能从类中搬移去除,这样可以简化原有的类。这样做更大的好处是有效地把类的核心职责和装饰功能区分开了。而且可以去除相关类中重复的装饰逻辑。

外观模式——买股票到买基金

外观模式(Facade),为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

结构图

image-20210819164110414

外观模式实现

外观类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Fund {
    private Stock1 stock1;
    private Stock2 stock2;
    private Stock3 stock3;

    public Fund() {
        stock1  = new Stock1();
        stock2  = new Stock2();
        stock3  = new Stock3();
    }

    public void buy(){
        stock1.buy();
        stock2.buy();
        stock3.buy();
    }

    public void sell(){
        stock1.sell();
        stock2.sell();
        stock3.sell();
    }

}

子类

1
2
3
4
5
6
7
8
9
public class Stock1 {
    public void buy(){
        System.out.println("buy 1");
    }

    public void sell(){
        System.out.println("sell 1");
    }
}
1
2
3
4
5
6
7
8
9
public class Stock2 {
    public void buy(){
        System.out.println("buy 2");
    }

    public void sell(){
        System.out.println("sell 2");
    }
}
1
2
3
4
5
6
7
8
9
public class Stock3 {
    public void buy(){
        System.out.println("buy 3");
    }

    public void sell(){
        System.out.println("sell 3");
    }
}

主函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class TemplateMethodTest {
    public static void main(String[] args) {
        Template person1 = new Person1();
        Template person2 = new Person2();
        person1.test1();
        person1.test2();
        person1.test3();
        person2.test1();
        person2.test2();
        person2.test3();
    }
}

何时使用外观模式

分三个阶段。

**首先,在设计初期阶段,应该要有意识的将不同的两个层分离,**比如经典的三层架构,就需要考虑在数据访问层和业务逻辑层、业务逻辑层和表示层的层与层之间建立外观Facade,这样可以为复杂的子系统提供一个简单的接口,使得耦合大大降低。

**其次,在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,**大多数的模式使用时也都会产生很多很小的类,这本是好事,但也给外部调用它们的用户程序带来了使用上的困难,增加外观 Facade 可以提供一个简单的接口,减少它们之间的依赖。

第三,在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了,但因为它包含非常重要的功能,新的需求开发必须要依赖于它。此时用外观模式Facade也是非常合适的。可以为新系统开发一个外观 Facade类,来提供设计粗糙或高度复杂的遗留代码的比较清晰简单的接口,让新系统与Facade对象交互,Facade 与遗留代码交互所有复杂的工作。

image-20210819165421229

对于复杂难以维护的老系统,直接去改或去扩展都可能产生很多问题,分两个小组,一个开发Facade与老系统的交互,另一个只要了解Facade的接口,直接开发新系统调用这些接口即可,可以减少很多不必要的麻烦。

享元模式——外包

享元模式(Flyweight),运用共享技术有效地支持大量细粒度的对象。

结构图

image-20210913202009120

享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能够受大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。

享元模式Flyweight执行时所需的状态是有内部的也可能有外部的,内部状态存储于ConcreteFlyweight对象之中,而外部对象则应该考虑由客户端对象存储或计算,当调用Flyweight对象的操作时,将该状态传递给它。

代码实现

Flyweight

1
2
3
4
5
6
7
8
9
public abstract class Flyweight {
    String name;

    public Flyweight(String name) {
        this.name = name;
    }

    abstract void use(String user);
}

ConcreteFlyweight

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class ConcreteFlyweight extends Flyweight{
    public ConcreteFlyweight(String name) {
        super(name);
    }

    @Override
    void use(String user) {
        System.out.println(super.name + " + " + user);
    }
}

FlyweightFactory

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class FlyweightFactory {

    HashMap<String,Flyweight> flyweights = new HashMap<>();

    public Flyweight getFlyweight(String name){
        if (!flyweights.containsKey(name)){
            flyweights.put(name,new ConcreteFlyweight(name));
        }
        return flyweights.get(name);
    }

    public int count(){
        return flyweights.size();
    }

}

Client

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public static void main(String[] args) {
    FlyweightFactory flyweightFactory = new FlyweightFactory();
    Flyweight ali = flyweightFactory.getFlyweight("ali");
    Flyweight ali1 = flyweightFactory.getFlyweight("ali");
    Flyweight taobao = flyweightFactory.getFlyweight("taobao");
    Flyweight taobao1 = flyweightFactory.getFlyweight("taobao");
    Flyweight zijie = flyweightFactory.getFlyweight("zijie");
    ali.use("bin1"); //ali + bin1
    ali1.use("bin2"); //ali + bin2
    taobao.use("bin3"); //taobao + bin3
    taobao1.use("bin4"); //taobao + bin4
    zijie.use("bin5"); //zijie + bin5
    System.out.println(flyweightFactory.count()); //3

}

代理模式——送花

代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问。

结构图

image-20210819095551401

代理模式实现

代理接口

1
2
3
4
5
6
7
public interface Person {
    void giveFlower();

    void giveGift();

    void singing();
}

代理类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Proxy implements Person{

    private Person real;

    public Proxy(Person real) {
        this.real = real;
    }

    @Override
    public void giveFlower() {
        real.giveFlower();
    }

    @Override
    public void giveGift() {
        real.giveGift();
    }

    @Override
    public void singing() {
        real.singing();
    }
}

被代理类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class RealPerson implements Person{
    @Override
    public void giveFlower() {
        System.out.println("送花");
    }

    @Override
    public void giveGift() {
        System.out.println("送礼物");
    }

    @Override
    public void singing() {
        System.out.println("唱歌");
    }
}

主函数

1
2
3
4
5
6
7
8
public class ProxyTest {
    public static void main(String[] args) {
        Proxy proxy = new Proxy(new RealPerson());
        proxy.giveFlower();
        proxy.giveGift();
        proxy.singing();
    }
}

应用场景

第一,**远程代理,也就是为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实。**WebService在.NET 中的应用中,当在应用程序的项目中加入一个Web引用,引用一个WebService,此时会在项目中生成一个WebReference的文件夹和一些文件,其实它们就是代理,这就使得客户端程序调用代理就可以解决远程访问的问题。

第二种应用是**虚拟代理,是根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。**这样就可以达到性能的最优化,比如打开一个很大的HTML网页时,里面可能有很多的文字和图片,但还是可以很快打开它,此时看到的是所有的文字,但图片却是一张一张地下载后才能看到。那些未打开的图片框,就是通过虚拟代理来替代了真实的图片,此时代理存储了真实图片的路径和尺寸。

第三种应用是**安全代理,用来控制真实对象访问时的权限。**一般用于对象应该有不同的访问权限的时候。

第四种是**智能指引,是指当调用真实的对象时,代理处理另外一些事。**如计算真实对象的引用次数,这样当该对象没有引用时,可以自动释放它;或当第一次引用一个持久对象时,将它装入内存;或在访问一个实际对象前,检查是否已经锁定它,以确保其他对象不能改变它。它们都是通过代理在访问一个对象时附加一些内务处理。