设计模式(二)结构型模式


Java 中一般认为有23 种设计模式,我们不需要所有的都会,但是其中常用的几种设计模式应该去掌握。
下面列出了所有的设计模式。需要掌握的设计模式我单独列出来了,当然能掌握的越多越好。
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

总结:每一种设计模式,代码去理解实现方式,根据结构图记忆理解。本文没有给出具体代码,可以在码云下载代码使用,本文适合不断翻看理解这些设计模式
码云代码:https://gitee.com/huopusa/arithmetic.git

六、适配器模式 adapter

分类
类适配器、对象适配器、接口适配器
UML图
这里写图片描述
适配器模式应用场景
类适配器与对象适配器的使用场景一致,仅仅是实现手段稍有区别,二者主要用于如下场景
(1)想要使用一个已经存在的类,但是它却不符合现有的接口规范,导致无法直接去访问,这时创建一个适配器就能间接去访问这个类中的方法。
(2)我们有一个类,想将其设计为可重用的类(可被多处访问),我们可以创建适配器来将这个类来适配其他没有提供合适接口的类。
接口适配器使用场景:
1)想要使用接口中的某个或某些方法,但是接口中有太多方法,我们要使用时必须实现接口并实现其中的所有方法,可以使用抽象类来实现接口,并不对方法进行实现(仅置空),然后我们再继承这个抽象类来通过重写想用的方法的方式来实现。这个抽象类就是适配器。
参照博文:
https://blog.csdn.net/yujin753/article/details/46287643
http://www.cnblogs.com/V1haoge/p/6479118.html

七、装饰器模式 decorator

https://www.cnblogs.com/jzb-blog/p/6717349.html

Component为统一接口这里写图片描述,也是装饰类和被装饰类的基本类型。
ConcreteComponent为具体实现类,也是被装饰类,他本身是个具有一些功能的完整的类。
Decorator是装饰类,实现了Component接口的同时还在内部维护了一个ConcreteComponent的实例,并可以通过构造函数初始化。而Decorator本身,通常采用默认实现,他的存在仅仅是一个声明:我要生产出一些用于装饰的子类了。而其子类才是赋有具体装饰效果的装饰产品类。
ConcreteDecorator是具体的装饰产品类,每一种装饰产品都具有特定的装饰效果。可以通过构造器声明装饰哪种类型的ConcreteComponent,从而对其进行装饰。
优点:
装饰器模式是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。
代码:

public interface ICar {
    void move();
}
//ConcreteComponent 具体构件角色(真实对象)
class Car implements ICar {

    @Override
    public void move() {
        System.out.println("陆地上跑!");
    }

}
class SuperCar implements ICar {
    private ICar car;
    public SuperCar(ICar car) {
        this.car = car;
    }
    @Override
    public void move() {
        car.move();
    }
}
//ConcreteDecorator具体装饰角色
class FlyCar extends SuperCar {
    public FlyCar(ICar car) {
        super(car);
    }
    public void fly() {
        System.out.println("天上飞");
    }
    @Override
    public void move() {
        super.move();
        fly();
    }
}
//ConcreteDecorator具体装饰角色
class WaterCar extends SuperCar {
    public WaterCar(ICar car) {
        super(car);
    }
    public void swim() {
        System.out.println("水里游");
    }
    @Override
    public void move() {
        super.move();
        swim();
    }
}
//ConcreteDecorator具体装饰角色
class AICar extends SuperCar {
    public AICar(ICar car) {
        super(car);
    }
    public void autoMove() {
        System.out.println("自动跑");
    }
    @Override
    public void move() {
        super.move();
        autoMove();
    }

测试客户端:

public class Client {
    public static void main(String[] args) {
        ICar car = new Car();
        car.move();

        System.out.println("------------增加新的功能:飞行");
        ICar flycar = new FlyCar(car);
        flycar.move();

        System.out.println("------------增加新的功能:水里游");
        ICar waterCar = new WaterCar(car);
        waterCar.move();

        System.out.println("------------增加两个新的功能,飞行,水里游");
        ICar waterCar2 = new WaterCar(flycar);
        waterCar2.move();

        System.out.println("------------累加3个新的功能,飞行,水里游,自动驾驶");
        ICar superCar = new AICar(waterCar2);
        superCar.move();
    }
}

运行结果:这里写图片描述

八、代理模式 proxy

特点:
1、 执行者、 被代理人
2、 对于被代理人来说, 这件事情是一定要做的, 但是我自己又不想做或者没有时间做, 找代理。
3、 需要获取到被代理的人个人资料。
4、关心过程
例子:
租房中介: 中介和你
火车票黄牛: 黄牛和你
媒人: 媒婆和你
明星经纪人: 经纪人和明星 刘德华要开演唱会(长沙) 、 准备工作和善后工作

AOP中使用场景
事务代理(声明式事务, 哪个方法需要加事务, 哪个方法不需要加事务)
日志监听
假如我们定义了一个service 方法
开启一个事务(open) 代理来做
事务的执行 执行我们的service方法
监听到是否有异常, 可能需要根据异常的类型来决定这个事务是否要回滚还是继续提交 代理来做
(commit/rollback) 代理来做
事务要关闭(close) 代理来做

参照
https://blog.csdn.net/qq_33214833/article/details/70230891
注:代理模式应用比较广泛,使代码开发更加灵活

九、外观模式 Facade (门面模式)

概念:
外观模式(Facade),他隐藏了系统的复杂性,并向客户端提供了一个可以访问系统的接口。这种类型的设计模式属于结构性模式。为子系统中的一组接口提供了一个统一的访问接口,这个接口使得子系统更容易被访问或者使用。
uml图
这里写图片描述
 简单来说,该模式就是把一些复杂的流程封装成一个接口供给外部用户更简单的使用。这个模式中,设计到3个角色。
  1).门面角色:外观模式的核心。它被客户角色调用,它熟悉子系统的功能。内部根据客户角色的需求预定了几种功能的组合。
  2).子系统角色:实现了子系统的功能。它对客户角色和Facade时未知的。它内部可以有系统内的相互交互,也可以由供外界调用的接口。
  3).客户角色:通过调用Facede来完成要实现的功能
注:因门面模式Spring+接口调用应用太广泛,没有写具体代码
十、桥接模式 bridge
https://www.cnblogs.com/lixiuyu/p/5923160.html

1.桥接模式的优点
(1)实现了抽象和实现部分的分离
桥接模式分离了抽象部分和实现部分,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,分别定义接口,这有助于系统进行分层设计,从而产生更好的结构化系统。对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了。
(2)更好的可扩展性
由于桥接模式把抽象部分和实现部分分离了,从而分别定义接口,这就使得抽象部分和实现部分可以分别独立扩展,而不会相互影响,大大的提供了系统的可扩展性。
(3)可动态的切换实现
由于桥接模式实现了抽象和实现的分离,所以在实现桥接模式时,就可以实现动态的选择和使用具体的实现。
(4)实现细节对客户端透明,可以对用户隐藏实现细节。
2.桥接模式的缺点
(1)桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程。
(2)桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性。
3.桥接模式的使用场景
(1)如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
(2)抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
(3)一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
(4)虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
(5)对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用
代码

/**
 * 定义接口--被实现者
 */
public interface Implementor {
    public void operation();
}
/**
 * 实现者A
 */
public class ConcreateImplementorA implements Implementor {
    @Override
    public void operation() {
        System.out.println("这个是ConcreateImplementorA的operation方法");
    }
}
/**
 * 实现者B
 */
public class ConcreateImplementorB implements Implementor {
    @Override
    public void operation() {
        System.out.println("这个是ConcreateImplementorB的operation方法");
    }
}
/**
 *  桥接类
 */
public abstract class Abstraction {
    private Implementor implementor;
    public Implementor getImplementor() {
        return implementor;
    }
    public void setImplementor(Implementor implementor){
        this.implementor = implementor;
    }
    // 引用接口
    protected void operation(){
        implementor.operation();
    }
}
/**
 * 桥接实现类
 */
public class RefinedAbstraction extends Abstraction {
    @Override
    protected void operation() {
        super.operation();
    }
}
/**
 * client 调用测试
 */
public class BridgeTest {
    public static void main(String[] args) {
        Abstraction abstraction = new RefinedAbstraction();

        //调用第一个实现类
        abstraction.setImplementor(new ConcreateImplementorA());
        abstraction.operation();

        //调用第二个实现类
        abstraction.setImplementor(new ConcreateImplementorB());
        abstraction.operation();
    }
}

十一、组合模式 Composite

1、UML结构
这里写图片描述

实例图这里写图片描述

2、角色组成
抽象构件角色(component):是组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。这个接口可 以用来管理所有的子对象。(可选)在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。
树叶构件角色(Leaf):在组合树中表示叶节点对象,叶节点没有子节点。并在组合中定义图元对象的行为。
树枝构件角色(Composite):定义有子部件的那些部件的行为。存储子部件。在Component接口中实现与子部件有关的操作。
客户角色(Client):通过component接口操纵组合部件的对象。

3、组合模式的优缺点
优点:
组合模式使得客户端代码可以一致地处理对象和对象容器,无需关系处理的单个对象,还是组合的对象容器。
将”客户代码与复杂的对象容器结构“解耦。
可以更容易地往组合对象中加入新的构件。
缺点: 使得设计更加复杂。客户端需要花更多时间理清类之间的层次关系。(这个是几乎所有设计模式所面临的问题)。
注意的问题:
有时候系统需要遍历一个树枝结构的子构件很多次,这时候可以考虑把遍历子构件的结构存储在父构件里面作为缓存。
客户端尽量不要直接调用树叶类中的方法(在我上面实现就是这样的,创建的是一个树枝的具体对象;),而是借用其父类(Graphics)的多态性完成调用,这样可以增加代码的复用性。
4、组合模式的使用场景
在以下情况下应该考虑使用组合模式:
当想表达对象的部分-整体的层次结构时。
希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象时。
参考:https://www.cnblogs.com/snaildev/p/7647190.html

十二、享元模式 Flyweight Pattern

http://www.cnblogs.com/java-my-life/archive/2012/04/26/2468499.html
享元模式:以共享的方式高效的支持大量的细粒度对象。通过复用内存中已存在的对象,降低系统创建对象实例的性能消耗。
java的 String 类型就是享元模式
这里写图片描述
单纯享元模式所涉及到的角色如下:
  ●  抽象享元(Flyweight)角色 :给出一个抽象接口,以规定出所有具体享元角色需要实现的方法。
  ●  具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定出的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。
  ●  享元工厂(FlyweightFactory)角色 :本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个符合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。
复合享元角色所涉及到的角色如下:
  ●  抽象享元(Flyweight)角色 :给出一个抽象接口,以规定出所有具体享元角色需要实现的方法。
  ●  具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定出的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。
  ●  复合享元(ConcreteCompositeFlyweight)角色 :复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合。复合享元角色又称作不可共享的享元对象。
  ●  享元工厂(FlyweightFactory)角色 :本角 色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有 一个符合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个 合适的享元对象。
享元模式的优缺点
  享元模式的优点在于它大幅度地降低内存中对象的数量。但是,它做到这一点所付出的代价也是很高的:
  ●  享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
  ●  享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。

享元模式实例
其实在Java中就存在这种类型的实例:String。
Java中将String类定义为final(不可改变的),JVM中字符串一般保存在字符串常量池中,这个字符串常量池在jdk 6.0以前是位于常量池中,位于永久代,而在JDK 7.0中,JVM将其从永久代拿出来放置于堆中。
  我们使用如下代码定义的两个字符串指向的其实是同一个字符串常量池中的字符串值。

String s1 = "abc";
String s2 = "abc";

如果我们以s1==s2进行比较的话所得结果为:true,因为s1和s2保存的是字符串常量池中的同一个字符串地址。这就类似于我们今天所讲述的享元模式,字符串一旦定义之后就可以被共享使用,因为他们是不可改变的,同时被多处调用也不会存在任何隐患。
享元模式使用的场景:
    当我们项目中创建很多对象,而且这些对象存在许多相同模块,这时,我们可以将这些相同的模块提取出来采用享元模式生成单一对象,再使用这个对象与之前的诸多对象进行配合使用,这样无疑会节省很多空间。

参照例子(更加形象的例子):https://www.cnblogs.com/V1haoge/p/6542449.html

  • 7
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是几种常见的Java结构型设计模式: 1. 适配器模式(Adapter Pattern):适配器模式用于将一个类的接口转换成客户端所期望的另一个接口。它允许不兼容的类能够合作。例如,将一个已有的类的接口转换成另一个接口,以满足客户端的需求。 ```java public interface MediaPlayer { public void play(String audioType, String fileName); } public interface AdvancedMediaPlayer { public void playVlc(String fileName); public void playMp4(String fileName); } public class VlcPlayer implements AdvancedMediaPlayer { public void playVlc(String fileName) { System.out.println("Playing vlc file. Name: " + fileName); } public void playMp4(String fileName) { // do nothing } } public class Mp4Player implements AdvancedMediaPlayer { public void playVlc(String fileName) { // do nothing } public void playMp4(String fileName) { System.out.println("Playing mp4 file. Name: " + fileName); } } public class MediaAdapter implements MediaPlayer { AdvancedMediaPlayer advancedMusicPlayer; public MediaAdapter(String audioType) { if (audioType.equalsIgnoreCase("vlc")) { advancedMusicPlayer = new VlcPlayer(); } else if (audioType.equalsIgnoreCase("mp4")) { advancedMusicPlayer = new Mp4Player(); } } public void play(String audioType, String fileName) { if (audioType.equalsIgnoreCase("vlc")) { advancedMusicPlayer.playVlc(fileName); } else if (audioType.equalsIgnoreCase("mp4")) { advancedMusicPlayer.playMp4(fileName); } } } public class AudioPlayer implements MediaPlayer { MediaAdapter mediaAdapter; public void play(String audioType, String fileName) { if (audioType.equalsIgnoreCase("mp3")) { System.out.println("Playing mp3 file. Name: " + fileName); } else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) { mediaAdapter = new MediaAdapter(audioType); mediaAdapter.play(audioType, fileName); } else { System.out.println("Invalid media. " + audioType + " format not supported"); } } } public class Main { public static void main(String[] args) { AudioPlayer audioPlayer = new AudioPlayer(); audioPlayer.play("mp3", "beyond the horizon.mp3"); audioPlayer.play("mp4", "alone.mp4"); audioPlayer.play("vlc", "far far away.vlc"); audioPlayer.play("avi", "mind me.avi"); } } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值