设计模式之结构型模式

261 阅读9分钟
结构型模式旨在通过改变代码结构来达到解耦的目的,使得我们的代码容易维护和扩展。

1.代理模式

优点: 1、职责清晰。 2、高扩展性。 3、智能化。

缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

使用场景:按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。

注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

public interface Buy {
    public void buy();
}

public class Boss implements Buy{
    @Override
    public void buy() {
        System.out.println( "boss买了一辆车");
    }
}

public class ProxyBoss implements Buy{
    private Boss boss;
    public ProxyBoss(Boss boss){
        this.boss = boss;
    }
    @Override
    public void buy() {
        System.out.println("这里就是代理增强");
        System.out.println("买车前给你送个媳妇儿");
        boss.buy();
        System.out.println("买完再给你送个儿子");
    }

    public static void main(String[] args) {
        ProxyBoss proxyBoss = new ProxyBoss(new Boss());
        proxyBoss.buy();
        /**
          * 这里就是代理增强
          * 买车前给你送个媳妇儿
          * 买了一辆车
          * 买完再给你送个儿子*/
    }
}

2.桥接模式

使用场景: 1、如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。 2、对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。 3、一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。

注意事项:对于两个独立变化的维度,使用桥接模式再适合不过了。

//接口
public interface DrawApi {
    public void draw(int radius,int x, int y);
}
public class BlackPen implements DrawApi {
    @Override
    public void draw(int radius, int x, int y) {
        System.out.println("用黑笔画,radius:" + radius + ",x:" + x + "y:" + y);
    }
}
public class RedPen implements DrawApi {
    @Override
    public void draw(int radius, int x, int y) {
        System.out.println("用红笔画,radius:"+radius+",x:"+x+"y:"+y);
    }
}

public abstract class Shape {
    protected DrawApi drawApi;

    protected Shape(DrawApi drawApi){
        this.drawApi = drawApi;
    }

    public abstract void draw();
}

public class Circle extends Shape {
    private int radius;

    public Circle(int radius, DrawApi drawAPI) {
        super(drawAPI);
        this.radius = radius;
    }

    @Override
    public void draw() {
        drawApi.draw(radius, 0, 0);
    }
}

public class Rectangle extends Shape {
    public int length;
    public int width;
    public Rectangle(DrawApi drawApi,int length,int width){
        super(drawApi);
        this.length=length;
        this.width=width;
    }
    @Override
    public void draw() {
        drawApi.draw(0,length,width);
    }
}

public class test {
    public static void main(String[] args) {
        Shape circle = new Circle(10, new BlackPen());
        circle.draw();//用黑笔画,radius:10,x:0y:0
        Shape rectangle = new Rectangle(new RedPen(), 2, 3);
        rectangle.draw();//用红笔画,radius:0,x:2y:3
    }
}

3.组合模式

使用场景:部分、整体场景,如树形菜单,文件、文件夹的管理。

注意事项:定义时为具体类。

何时使用: 1、您想表示对象的部分-整体层次结构(树形结构)。 2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

public class Emplyee {
    private String name;
    private String dept;
    private int salary;
    private List<Emplyee> subordinates; // 下属

    public Emplyee(String name,String dept, int sal) {
        this.name = name;
        this.dept = dept;
        this.salary = sal;
        subordinates = new ArrayList<Emplyee>();
    }

    public void add(Emplyee e) {
        subordinates.add(e);
    }

    public void remove(Emplyee e) {
        subordinates.remove(e);
    }

    public List<Emplyee> getSubordinates(){
        return subordinates;
    }

    @Override
    public String toString(){
        return ("Employee :[ Name : " + name + ", dept : " + dept + ", salary :" + salary+" ]");
    }

}

4.装饰模式

允许向一个现有的对象添加新的功能,同时又不改变其结构。

使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。

注意事项:可代替继承。

优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

缺点:多层装饰比较复杂

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

主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

何时使用:在不想增加很多子类的情况下扩展类。

public abstract class Skewer {
    // 返回描述
    public abstract String getDescription();
    // 返回价格
    public abstract double cost();
}

public class PorkSkewer extends Skewer {
    @Override
    public String getDescription() {
        return "猪肉串";
    }

    @Override
    public double cost() {
        return 3.3;
    }
}

public class MuttonSkewer extends Skewer {
    @Override
    public String getDescription() {
        return "羊肉串";
    }

    @Override
    public double cost() {
        return 6.6;
    }
}

public abstract class Condiment extends Skewer{

}

public class HotCondiment extends Condiment {
    private Skewer skewer;
    public HotCondiment(Skewer skewer){
        this.skewer = skewer;
    }
    @Override
    public String getDescription() {
        return skewer.getDescription()+"放点辣椒";
    }

    @Override
    public double cost() {
        return skewer.cost()+10;
    }
}

public class ZiranCondiment extends Condiment {
    private Skewer skewer;
    public ZiranCondiment(Skewer skewer){
        this.skewer=skewer;
    }
    @Override
    public String getDescription() {
        return skewer.getDescription()+"放点孜然";
    }

    @Override
    public double cost() {
        return skewer.cost()+20;
    }
}

public class test {
    public static void main(String[] args) {
        Condiment condiment = new HotCondiment(new MuttonSkewer());
        String description = condiment.getDescription();
        System.out.println(description);//羊肉串放点辣椒

        double cost = condiment.cost();
        System.out.println(cost);//16.6
    }
}

5.外观模式

隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口

主要解决:降低访问复杂系统的内部子系统时的复杂度,简化客户端与之的接口。

何时使用: 1、客户端不需要知道系统内部的复杂联系,整个系统只需提供一个"接待员"即可。 2、定义系统的入口。

优点: 1、减少系统相互依赖。 2、提高灵活性。 3、提高了安全性。

缺点:不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。

使用场景: 1、为复杂的模块或子系统提供外界访问的模块。 2、子系统相对独立。 3、预防低水平人员带来的风险。

注意事项:在层次化结构中,可以使用外观模式定义系统中每一层的入口。

public interface Shape {
    void draw();
}

public class Circle implements Shape{
    @Override
    public void draw() {
        System.out.println("画个圆");
    }
}

public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("画个长方形");
    }
}

public class ShapeMarker {
    private Shape circle;
    private Shape rectangle;

    public ShapeMarker(){
        circle=new Circle();
        rectangle=new Rectangle();
    }
    public void circleDraw(){
        circle.draw();
    }
    public void rectangle(){
        rectangle.draw();
    }
}

public class Test {
    public static void main(String[] args) {
        ShapeMarker shapeMarker = new ShapeMarker();
        shapeMarker.circleDraw();//画个圆
        shapeMarker.rectangle();//画个长方形
    }
}

6.享元模式

主要用于减少创建对象的数量,以减少内存占用和提高性能。

主要解决:在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。

何时使用: 1、系统中有大量对象。 2、这些对象消耗大量内存。 3、这些对象的状态大部分可以外部化。 4、这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。 5、系统不依赖于这些对象身份,这些对象是不可分辨的。

如何解决:用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。

优点:大大减少对象的创建,降低系统的内存,使效率提高。

缺点:提高了系统的负责度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

使用场景: 1、系统有大量相似对象。 2、需要缓冲池的场景。

注意事项: 1、注意划分外部状态和内部状态,否则可能会引起线程安全问题。 2、这些类必须有一个工厂对象加以控制。

public interface Shape {
    void draw();
}

public class Circle implements Shape{
    private String color;
    private int x;
    private int y;
    private int radius;

    public Circle(String color){
        this.color = color;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }

    public void setRadius(int radius) {
        this.radius = radius;
    }

    @Override
    public void draw() {
        System.out.println("Circle: Draw() [Color : " + color
                +", x : " + x +", y :" + y +", radius :" + radius);
    }
}    

public class ShapeFactory {
    private static final HashMap<String, Shape> circleMap = new HashMap();

    public static Shape getCircle(String color) {
        Circle circle = (Circle)circleMap.get(color);

        if(circle == null) {
            circle = new Circle(color);
            circleMap.put(color, circle);
            System.out.println("Creating circle of color : " + color);
        }
        return circle;
    }
}

public class Test {
    private static final String colors[] =
            {"Red", "Green", "Blue", "White", "Black"};

    public static void main(String[] args) {

        for (int i = 0; i < 20; ++i) {
            Circle circle = (Circle) ShapeFactory.getCircle(getRandomColor());
            circle.setX(getRandomX());
            circle.setY(getRandomY());
            circle.setRadius(100);
            circle.draw();
        }
    }

    private static String getRandomColor() {
        return colors[(int) (Math.random() * colors.length)];
    }

    private static int getRandomX() {
        return (int) (Math.random() * 100);
    }

    private static int getRandomY() {
        return (int) (Math.random() * 100);
    }
}

7.适配器模式

作为两个不兼容的接口之间的桥梁,它结合了两个独立接口的功能

优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。

缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。

使用场景:有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。

注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。

public interface Duck {
    public void quack(); // 鸭的呱呱叫
      public void fly(); // 飞
}

public interface Check{
    public void gobble(); // 鸡的咕咕叫
      public void fly(); // 飞
}
public class WildCheck implements Check{    public void gobble() {
        System.out.println("咕咕叫");
    }
      public void fly() {
        System.out.println("鸡也会飞哦");
    }
}

// 毫无疑问,首先,这个适配器肯定需要 implements Duck,这样才能当做鸭来用
public class CheckAdapter implements Duck {
    Check check;    // 构造方法中需要一个鸡的实例,此类就是将这只鸡适配成鸭来用
      public CheckAdapter(Check check) {        this.check= check;    }

    // 实现鸭的呱呱叫方法
      @Override
      public void quack() {
        // 内部其实是一只鸡的咕咕叫
        check.gobble();    }

      @Override
      public void fly() {
        check.fly();    }
}

public static void main(String[] args) {
    // 有一只野鸡
      Check wildCheck  = new WildCheck ();      // 成功将野鸡适配成鸭
      Duck duck = new Check Adapter(wildCheck );      ...
}