设计模式

单例模式

  • 所谓单例模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)

饿汉式(静态常量)

  • 构造器私有化
  • 类的内部创建对象
  • 向外暴露一个静态的公共方法
/**
 * 饿汉式(静态常量)
 */
public class Singleton01 {
    public static void main(String[] args) {
        //测试
        Singleton instance = Singleton.getInstance();
    }
}

class Singleton{
    //1 构造器私有化
    private Singleton(){

    }
    //2 本类内部创建对象实例
    private final static Singleton instance = new Singleton();

    //3 提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance(){
        return instance;
    }
}
  • 优点
    • 写法比较简单,就是在类装载的时候就完成实例化。避免了下城同步问题
  • 缺点
    • 在类装载的时候就完成实例化,没有达到lazy loading的效果。如果从始至终都没用过这个实例,则会造成内存的浪费。
  • 这种方式基于classloader机制避免了多线程的同步问题,不过,instance在装载就实例化,在单例模式中大多数都是调用getInstace方法,但是导致类装载的原因有很多种,因此不能确定有其他方式(或其他静态方法)导致类装载,这时候初始化instance就没有达到lazy loading的效果
  • 这种单例模式可用,可能造成内存浪费

饿汉式(静态代码块)

/**
 * 饿汉式(静态代码块)
 */
public class Singleton02 {
    public static void main(String[] args) {
        //测试
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance.hashCode() == instance1.hashCode());
    }
}

class Singleton{
    //1 构造器私有化
    private Singleton(){

    }
    //2 本类内部创建对象实例
    private  static Singleton instance;

    //在静态代码块种创建对象实例
    static {
        instance = new Singleton();
    }

    //3 提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance(){
        return instance;
    }
}
  • 静态代码块和静态常量类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
  • 这种单例模式可用,但是可能造成内存浪费

懒汉式(线程不安全)

/**
 * 懒汉式(线程不安全)
 */
public class Singleton03 {
    public static void main(String[] args) {
        //测试
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance.hashCode() == instance1.hashCode());
    }
}

class Singleton{
    private static Singleton instance;

    private Singleton(){

    }
    //提供静态的公用方法,当使用该方法时,才创建实例
    public static Singleton getInstance(){
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}
  • 起到了lazy loading的效果,但是只能在单线程下使用
  • 在多线程下,一个线程进入了if(instance == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时会产生多个实例
  • 在实际开发中,不能使用这种方式

懒汉式(线程安全)

/**
 * 懒汉式(线程安全,同步方法)
 */
class Singleton{
    private static Singleton instance;

    private Singleton(){

    }
    //使用synchronized解决线程安全问题
    public static synchronized Singleton getInstance(){
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}
  • 解决了线程不安全问题
  • 效率太低了,每个线程在向获得类的实例的时候,执行getInstance()都要进行同步。而其实这个方法只执行一个实例化代码就够了,后面的想获得该类实例直接return就行了
  • 在实际开发中不推荐使用这种方法

双重检查

/**
 * 双重检查
 */
class Singleton{
    private static volatile Singleton instance;

    private Singleton(){

    }
    //加入双重检查代码,解决线程安全问题,通知解决懒加载问题
    public static  Singleton getInstance(){
        if (instance == null){
            synchronized (Singleton.class){
                if (instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 双重检查概念是多线程开发中常使用到的,如代码中所示,进行了两次if(singleton == null)检查,这样就可以保证线程安全了
  • 实例化代码只用执行一次,后面再次访问直接return实例化对象
  • 线程安全;延迟加载;效率较高
  • 在实际开发中,推荐使用这种单例设计模式

静态内部类

/**
 * 静态内部类
 */
class Singleton{
    private static volatile Singleton instance;

    private Singleton(){

    }
    //静态内部类
    //Singleton类装载的时候内部类不会装载
    //类装载是线程安全的
    private static class SingletonInstance{
        private static final  Singleton instance = new Singleton();
    }

    public static  Singleton getInstance(){
        return SingletonInstance.instance;
    }
}
  • 这种方式采用了类装载机制来保证初始化实例时只有一个线程
  • 静态内部类方式在Singleton类被状态时并不会立即实例化,而是在需要实例化时,调用getInstance(),才会装载SingletonInstance类,从而完成Singleton的实例化
  • 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程无法进入的。
  • 在实际开发中,推荐使用

枚举

public class Singleton07 {
    public static void main(String[] args) {
        //测试
        Singleton instance = Singleton.INSTANCE;
        Singleton instance1 = Singleton.INSTANCE;
        System.out.println(instance.hashCode() == instance1.hashCode());
    }
}

/**
 * 枚举
 */
enum  Singleton{
    INSTANCE;
}

工厂模式

  • 需求,一个披萨项目要便于披萨种类的扩展,要便于维护
    • 披萨的种类很多(比如GreekPizz、CheesePizz等)
    • 披萨的制作有prepare,bake,cut,box
    • 完成披萨店订购功能

  • 传统模式

  • Pizza抽象类
//将Pizza类做成抽象类
public abstract class Pizza {
    //披萨名字
    protected String name;

    //准备工作,不同的披萨材料不一样,做成抽象方法
    public abstract void prepare();

    //烘烤
    public void bake() {
        System.out.println(name + " baking;");
    }

    //切割
    public void cut() {
        System.out.println(name + " cutting;");
    }

    //打包
    public void box() {
        System.out.println(name + " boxing;");
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • 奶酪披萨继承披萨抽象类
//奶酪披萨
public class CheesePizza extends Pizza{
    public void prepare() {
        System.out.println("给制作奶酪披萨准备原材料");
    }
}
  • 希腊披萨继承披萨抽象类
//希腊披萨
public class GreekPizza extends Pizza{
    public void prepare() {
        System.out.println("给制作希腊披萨准备原材料");
    }
}
  • 制作披萨
class PizzaStore {
    public static void main(String[] args) {
        getPizza("cheese");
    }

    public static Pizza getPizza(String type){
        Pizza pizza = null;
        if (type.equals("greek")) {
            pizza = new GreekPizza();
            pizza.setName("希腊披萨");
        }else if (type.equals("cheese")){
            pizza = new CheesePizza();
            pizza.setName("奶酪披萨");
        }
        //输出披萨制作过程
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}
  • 使用传统方式比较好理解,简单易操作
  • 缺点是违反了设计模式的ocp原则,即对外扩展开放,对修改关闭。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码
  • 比如新增一个种类的披萨,我们需要改OrderPizza.java的代码

  • 改进的思路分析
    • 修改代码可以接受,但是如果我们在其他的地方也有创建Pizza的代码,就意味着,也需要修改,而创建Pizza的代码,往往有多处
  • 思路
    • 把创建爱你Pizza对象封装到一个类中,这样我们有新的Pizza中类时,只需要修改该类就可,其他创建到Pizza对象的代码就不需要修改

简单工厂模式

  • 简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
  • 简单工厂模式:定义了一个创建对象的类,有这个类来封装实例化对象的行为(代码)
  • 在软件开发中,当我们会使用到大量的创建某种、某类、或者某批对象时,就会使用到工厂模式

  • 工厂类
//简单工厂
public class SimpleFactory {
    //根据orderType返回对应的Pizza对象
    public Pizza createPizza(String orderType){
        Pizza pizza = null;
        if (orderType.equals("greek")) {
            pizza = new GreekPizza();
            pizza.setName("希腊披萨");
        }else if (orderType.equals("cheese")){
            pizza = new CheesePizza();
            pizza.setName("奶酪披萨");
        }
        return pizza;
    }
}
  • 使用工厂类制作披萨
class PizzaStore {
    public static void main(String[] args) {
        getPizza("cheese");
    }

    public static Pizza getPizza(String type){
        //使用简单工厂模式创建披萨
        SimpleFactory simpleFactory = new SimpleFactory();
        Pizza pizza = simpleFactory.createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.bake();
        return pizza;
    }
}

工厂方法模式

  • 新需求
    • 客户在点披萨的时,可以点不同口味的披萨,比如北京奶酪披萨、北京的胡椒披萨或者是伦敦的奶酪披萨、伦敦的胡椒披萨
  • 思路一
    • 使用简单工厂模式,创建不同的简单工厂类,比如BJPizzaSimpleFactory、LDPizzaSimpleFactory,从当前这个案例来说也是可以的,但是考虑到项目的规模,以及软件的可维护性、可扩展性并不是特别好
  • 思路二
    • 使用工厂方法模式

  • 工厂方法模式设计方案:将披萨项目的实例化功能抽象成抽象方法,在不同的口味点餐子类中具体实现
  • 工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类

  • Pizza抽象类
//将Pizza类做成抽象类
public abstract class Pizza {
    //披萨名字
    protected String name;

    //准备工作,不同的披萨材料不一样,做成抽象方法
    public abstract void prepare();

    //烘烤
    public void bake() {
        System.out.println(name + " baking;");
    }

    //切割
    public void cut() {
        System.out.println(name + " cutting;");
    }

    //打包
    public void box() {
        System.out.println(name + " boxing;");
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • 北京奶酪披萨
//北京奶酪披萨
public class BJCheesePizza extends Pizza {
    public void prepare() {
        System.out.println("给制作北京奶酪披萨准备原材料");
    }
}
  • 北京胡椒披萨
//北京胡椒披萨
public class BJPepperPizza extends Pizza {
    public void prepare() {
        System.out.println("给制作北京胡椒披萨准备原材料");
    }
}
  • 伦敦奶酪披萨
//伦敦奶酪披萨
public class LDCheesePizza extends Pizza {
    public void prepare() {
        System.out.println("给制作伦敦奶酪披萨准备原材料");
    }
}
  • 伦敦胡椒披萨
//伦敦胡椒披萨
public class LDPepperPizza extends Pizza {
    public void prepare() {
        System.out.println("给制作伦敦胡椒披萨准备原材料");
    }
}

  • 创建一个抽象类,定义制作披萨的方法有子类实现
//方法工厂模式
public abstract class OrderPizza {
    //根据orderType返回对应的Pizza对象
    abstract Pizza createPizza(String type);
}
  • 北京订购披萨的工厂类(BJOrderPizza.java)
//北京订购披萨工厂
public class BJOrderPizza extends OrderPizza{
    Pizza createPizza(String type) {
        Pizza pizza = null;
        if (type.equals("cheese")){
            pizza = new BJCheesePizza();
            pizza.setName("北京奶酪披萨");
        }else if(type.equals("pepper")){
            pizza = new BJPepperPizza();
            pizza.setName("北京胡椒披萨");
        }
        return pizza;
    }
}
  • 伦敦订购披萨工厂
//伦敦订购披萨工厂
public class LDOrderPizza extends OrderPizza{
    Pizza createPizza(String type) {
        Pizza pizza = null;
        if (type.equals("cheese")){
            pizza = new LDCheesePizza();
            pizza.setName("伦敦奶酪披萨");
        }else if(type.equals("pepper")){
            pizza = new LDPepperPizza();
            pizza.setName("伦敦胡椒披萨");
        }
        return pizza;
    }
}
  • 测试,使用订购北京披萨的工厂订购北京奶酪披萨
class PizzaStore {
    public static void main(String[] args) {
        Pizza pizza = new BJOrderPizza().createPizza("cheese");
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
    }
}
  • 控制台输出

原型模式

  • 概述
    • 用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象
  • 结构
    • 抽象原型类:规定了具体原型对象必须实现的clone()方法
    • 具体原型类:实现抽象原型类clone()方法,它是可被复制的
    • 访问类:使用具体原型类中的clone()方法来复制新的对象

  • 原型模式的克隆分为浅克隆和深克隆

    • 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型操作,仍指向原有属性所指向的对象的内存地址
    • 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址
  • Java中的Object类提供了clone()方法来实现浅克隆。cloneable接口是上面的类图中的抽象类原型,而实现了Cloneable接口的子实现类就是具体的原型类

  • Realizetype.java

public class Realizetype implements Cloneable{
    //重写clone方法
    @Override
    protected Realizetype clone() throws CloneNotSupportedException {
        System.out.println("具体原型复制成功");
        return (Realizetype)super.clone();
    }
}
  • client.java
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        //创建一个原型类
        Realizetype realizetype = new Realizetype();
        //调用原型类中的clone方法进行对象的克隆
        Realizetype clone = realizetype.clone();

        //原型对象和克隆出来的对象不是同一个对象
        System.out.println(realizetype == clone); // false

    }
}
  • 案例
    • 用原型模式生成三好学生奖状
    • 同一学校三好学生奖状除了获奖人姓名不同,其他都相同,可以使用原型模式复制多个三好学生奖状出来,然后再修改奖状上的名字即可

  • 具体原型类
//奖状(浅克隆)
public class Citation implements Cloneable{

    private String name;

    @Override
    protected Citation clone() throws CloneNotSupportedException {
        return (Citation)super.clone();
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • 创建原型对象并克隆
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        //创建原型对象
        Citation citation = new Citation();
        //克隆奖状
        Citation student1 = citation.clone();
        //给奖状赋学生姓名
        student1.setName("学生001");
        Citation student2 = citation.clone();
        student2.setName("学生002");
    }
}
  • 使用场景
    • 对象的创建非常复杂,可以使用原型模式快捷创建对象
    • 性能和安全要求比较高

  • 深克隆,将上面奖状类的name属性修改为Student类型的属性

  • Student.java
public class Student implements Serializable {
    private String name;
    
    public Student(String name){
        this.name = name;
    }

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

    public String getName() {
        return name;
    }
}
  • 具体原型类,奖状
//奖状,深克隆
public class Citation implements Cloneable{

    private Student student;

    @Override
    protected Citation clone() throws CloneNotSupportedException {
        return (Citation)super.clone();
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    public Student getStudent() {
        return student;
    }
}
  • 创建原型对象并克隆
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        //创建原型对象
        Citation citation = new Citation();
        Student student = new Student("张三");
        citation.setStudent(student);
        //克隆奖状
        Citation citation1 = citation.clone();
        citation1.getStudent().setName("李四");

        //判断citation对象和citation对象是否是同一对象
        System.out.println(citation.getStudent().getName());  //李四
        System.out.println(citation1.getStudent().getName()); //李四

    }
}
  • 存在问题克隆出来的奖状对象的Studnet和原型对象中的Student是统一个对象(浅克隆)

  • 深克隆(使用对象输入输出流)
public class Client {
    public static void main(String[] args) throws Exception {
        //创建原型对象
        Citation citation = new Citation();
        Student student = new Student("张三");
        citation.setStudent(student);
        //克隆奖状

        //1.将原型对象写入文件中
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\temp\\a.txt"));
        oos.writeObject(citation);
        oos.close();

        //2.从文件中读取,再修改student的姓名
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\temp\\a.txt"));
        Citation citation1 = (Citation)ois.readObject();
        citation1.getStudent().setName("李四");
        ois.close();

        System.out.println(citation.getStudent().getName());  //张三
        System.out.println(citation1.getStudent().getName()); //李四
    }
}

建造者模式

  • 将一个复杂对象的构建与表示分离,使得同样的构造过程可以创建不同的表示
  • 分离了部件的构造(由Builder来负责)。从而可以构造复杂的对象。这个模式适用于:某个对象的构造过程复杂的情况
  • 由于实现了构造和装配的解耦。不同构造器,相同的装配,也可以做出不同的对象;相同的构造器,不同的装配顺序也可以做出不同的对象。也就是实现了构造算法、装配算法的解耦,实现了更好的复用。
  • 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象类型就可以得到该对象,而无需知道其内部的具体构造细节

  • 建造者(Builder)模式包含如下角色
    • 抽象建造类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建
    • 具体建造类(ConcreteBuilder):实现Builder接口,完成复产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例
    • 产品类(Product):要创建的复杂对象
    • 指挥者类(Director):调用具体建造者类创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建

  • 实例
    • 创建共享单车
    • 生产自行和是一个复杂的过程,它包含了车架,车座等组件的生产。而车架有碳纤维,铝合金等材质,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式
    • 这里Bike是产品,包括车架,车座等组件。Builder是抽象建造者,MobikeBuilder和OfoBuilder是具体的建造者。Director是指挥者。类图如下

  • 自行车类
//自行车(产品类)
public class Bike {
    private String frame;
    private String seat;

    public String getFrame() {
        return frame;
    }

    public void setFrame(String frame) {
        this.frame = frame;
    }

    public String getSeat() {
        return seat;
    }

    public void setSeat(String seat) {
        this.seat = seat;
    }
}
  • 抽象Builder类
//抽象Builder类
public abstract class Builder {
    protected Bike mBike = new Bike();

    public abstract void buildFrame();

    public abstract void buildSeat();

    public abstract Bike createBike();
}
  • 摩拜单车类Builder类,继承Builder
//摩拜单车
public class MobikeBuilder extends Builder{
    public void buildFrame() {
        mBike.setFrame("碳纤维车架");
    }

    public void buildSeat() {
        mBike.setSeat("橡胶车座");
    }

    public Bike createBike() {
        return mBike;
    }
}
  • Ofo单车Builder类,继承Builder
//Ofo单车
public class OfoBikeBuilder extends Builder{
    public void buildFrame() {
        mBike.setFrame("铝合金车架");
    }

    public void buildSeat() {
        mBike.setSeat("真皮车座");
    }

    public Bike createBike() {
        return mBike;
    }
}
  • 指挥者类
//指挥者类
public class Directory {

    private Builder mBuilder;

    public Directory(Builder builder){
        mBuilder = builder;
    }

    public Bike construct(){
        mBuilder.buildFrame();
        mBuilder.buildSeat();
        return mBuilder.createBike();
    }
}
  • 测试
public class Client {
    public static void main(String[] args) {
        Directory directory = new Directory(new OfoBikeBuilder());
        Bike bike = directory.construct();
        System.out.println(bike.getFrame() + " " + bike.getSeat());

        Directory directory1 = new Directory(new MobikeBuilder());
        Bike bike1 = directory1.construct();
        System.out.println(bike1.getFrame() + " " + bike1.getSeat());

    }
}
  • 优点
    • 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性
    • 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
    • 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程
    • 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则
  • 缺点
    • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制

  • 使用场景
    • 建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临剧烈的变化,但将它们组合在一起的算法却相对稳定,所有它通常在以下场合使用
      • 创建对象较复杂,有多个部件构成,各部件面临着复杂的变化,但构建的建造顺序是稳定的。
      • 创建复杂对象的算法独立于该对象的组成部分以及他们的装配方式,即产品的构建过程和最终的表示是独立的

  • 工厂方法模式VS建造者模式
    • 工厂方法模式注重的是整体对象的创建方法;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象
    • 例子,如果使用工厂方法模式,直接产生出来一个超人,而使用建造者模式,则需要组装手、头、脚、躯干等步骤,最终才生成一个超人

代理模式

  • 由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问和目标对象之间的中介
  • Java中的代理对象按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK动态代理和CGLib代理两种

- 结构 - 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法 - 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象 - 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能
  • 静态代理
    • 案例:火车站卖票
    • 如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。

  • 卖票接口
//卖票接口
public interface SellTickets {
    void sell();
}
  • 火车站类
//火车站 火车站具有卖票功能,实现SellTickets接口
public class TrainStation implements SellTickets{
    public void sell() {
        System.out.println("火车站卖票");
    }
}
  • 代售点
//代售点
public class ProxyPoint implements SellTickets{

    private TrainStation trainStation = new TrainStation();

    public void sell() {
        System.out.println("代理点收取一些服务费");
        trainStation.sell();
    }
}
  • 测试,使用代售点购票
//测试类
public class Client {
    public static void main(String[] args) {
        ProxyPoint proxyPoint = new ProxyPoint();
        proxyPoint.sell();
        //输出...
        //代理点收取一些服务费
        //火车站卖票
    }
}
  • JDK动态代理
public class ProxyFactory {
    TrainStation station = new TrainStation();

    public SellTickets getProxyObject(){
        /**
         * 使用Proxy获取代理对象
         * newProxyInstance()方法参数说明
         * 参数1:ClassLoader 类加载器,用于加载动态类,使用真实对象的类加载器即可
         * 参数2:Class<?>[] interfaces 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口
         * 参数3:InvocationHandler 代理对象的调用处理程序
         */
        SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     *
                     * @param proxy 代理对象
                     * @param method 对应于在代理对象上调用的接口方法的Method
                     * @param args 代理对象调用接口方法时传递的实际参数
                     * @return
                     * @throws Throwable
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("代售点收取一部分服务费");
                        //执行真实对象
                        return method.invoke(station);
                    }
                });
        return sellTickets;
    }
}
  • 测试
public class Client {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        SellTickets proxyObject = proxyFactory.getProxyObject();
        proxyObject.sell();
        //代售点收取一部分服务费
        //火车站卖票
    }
}
  • 静态代理和动态代理对比

    • 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转
    • 如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题。
  • 优缺点

    • 优点:代理模式在客户端与目标对象之间起到了一个中介的作用和保护目标对象的作用,代理对象可以扩展目标对象的功能,代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度
    • 缺点:增加了系统的复杂度

适配器模式

  • 定义

    • 将一个类的接口转换成客户希望的另外一个接口,使得原来由于接口不兼容而不能一起工作的那些类能够一起工作
    • 适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些
  • 结构

    • 目标接口(Adapter):当前系统业务所期待的接口,它可以是抽象类或接口
    • 适配者类(Adaptee):被访问和是适配的现存组件库中的组件接口
    • 适配器(Adapter):是一个转换器,通过继承或引用适配者的对象,把适配者接口转换为目标接口,让客户按目标接口的格式访问适配者

  • 类适配器模式
    • 定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已存在的组件
  • 案例:读卡器
    • 现有一台电脑只能读取SD卡,而要读取TF卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将TF卡中的内容读取出来

  • SD卡接口
public interface SDCard {
    String readSD();

    void writeSD();
}
  • SD卡实现类
public class SDCardImpl implements SDCard{
    public String readSD() {
        return "sd card read";
    }

    public void writeSD() {
        System.out.println("sd card write");
    }
}
  • TF卡接口
public interface TFCard {
    String readTF();

    void writeTF();
}
  • TF卡实现类
public class TFCardImpl implements TFCard{
    public String readTF() {
        return "tf card read";
    }

    public void writeTF() {
        System.out.println("tf cart write");
    }
}
  • 电脑类,读取SD卡
public class Computer {
    public String readSD(SDCard sdCard){
        return sdCard.readSD();
    }
}
  • TF卡适配器,继承TF卡实现类,并实现SD卡接口,当调用SD卡接口的时候去调用TF卡实现类的方法,实现适配
public class TFAdapter extends TFCardImpl implements SDCard {
    public String readSD() {
        return readTF();
    }

    public void writeSD() {
        writeTF();
    }
}
  • 测试
public class Client {
    public static void main(String[] args) {
        Computer computer = new Computer();
        String msg = computer.readSD(new TFAdapter());
        System.out.println(msg);  //tf card read
    }
}

  • 对象适配器模式,不继承TFCard实现类,直接将TFCard接口注入进来
public class TFAdapterSD implements SDCard{

    private TFCard tfCard;

    public TFAdapterSD(TFCard tfCard){
        this.tfCard = tfCard;
    }

    public String readSD() {
        return tfCard.readTF();
    }

    public void writeSD() {
        tfCard.writeTF();
    }
}
  • 测试
public class Client {
    public static void main(String[] args) {
        Computer computer = new Computer();
        String s = computer.readSD(new TFAdapterSD(new TFCardImpl()));
        System.out.println(s);
    }
}
  • 应用场景
    • 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
    • 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同

装饰者模式

  • 案例
    • 快餐店有炒饭、炒面这些快餐,可以额外附加鸡蛋、培根这些配菜,加配菜需要额外价钱,每个配餐的价格通常不一样,那么计算总价就会显得比较麻烦
  • 使用继承的方式存在的问题
    • 扩展性不好
      • 如果要再加一种配料,我们就会发现需要给FiredRice和FiredNoodles分别定义一个子类。如果再新增一个快餐品类(炒河粉)的话,就需要定义更多的子类
    • 产生过多的子类

  • 定义
    • 在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式

  • 结构
    • 抽象构建角色:定义一个抽象接口以规范准备接收附加责任的对象
    • 具体构建角色:实现抽象构建,通过装饰角色为其条件一些职责
    • 抽象装饰角色:继承或实现抽象构建,并包含具体构建的实例,可以通过其子类扩展具体构建的功能
    • 具体装饰角色:实现抽象装饰的相关方法,并给具体构建对象添加附加的职责

  • 快餐抽象类
//快餐抽象类(抽象构建)
public abstract class FastFood {
    //价格
    private float price;
    //描述
    private String desc;


    public FastFood(float price,String desc){
        this.price = price;
        this.desc = desc;
    }

    //获取价格
    public abstract float cost();

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}
  • 炒饭类
//炒饭(具体构建)
public class FiredRice extends FastFood {

    public FiredRice() {
        super(10, "炒饭");
    }

    public float cost() {
        return getPrice();
    }
}
  • 炒面类
//炒面(具体构建)
public class FiredNoodles extends FastFood{

    public FiredNoodles(float price, String desc) {
        super(12, "炒面");
    }

    public float cost() {
        return getPrice();
    }
}
  • 装饰者类
//配料类(抽象装饰)
public abstract class Garnish extends FastFood{

    private FastFood fastFood;

    public Garnish(FastFood fastFood,float price, String desc) {
        super(price, desc);
        this.fastFood = fastFood;
    }

    public abstract float cost();

    public FastFood getFastFood() {
        return fastFood;
    }
}
  • 鸡蛋类,具体装饰
//鸡蛋配料(具体装饰)
public class Egg extends Garnish{

    public Egg(FastFood fastFood) {
        super(fastFood, 1, "鸡蛋");
    }

    public float cost() {
        return getFastFood().cost() + 1;
    }

    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}
  • 培根类,具体装饰
//培根配料(具体装饰)
public class Bacon extends Garnish {
    public Bacon(FastFood fastFood) {
        super(fastFood, 1, "培根");
    }

    public float cost() {
        return getFastFood().cost() + 1;
    }

    @Override
    public String getDesc() {
        return "培根" + getFastFood().getDesc();
    }
}
  • 测试
public class Client {
    public static void main(String[] args) {
        //点一份炒饭
        FastFood food = new FiredRice();
        //花费的价格
        System.out.println(food.getDesc() + " " + food.cost() + "元"); //鸡蛋炒饭 11.0元

        System.out.println("====================");

        //点一份鸡蛋炒饭
        FastFood food1 = new Egg(food);
        //花费的价格
        System.out.println(food1.getDesc() + " " + food1.cost() + "元"); //鸡蛋炒饭 11.0元

        System.out.println("====================");

        //点一根培根
        FastFood food2 = new Bacon(food1);
        //花费的价格
        System.out.println(food2.getDesc() + " " + food2.cost() + "元");   //培根鸡蛋炒饭 12.0元
    }
}
  • 装饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样式的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任
  • 装饰类和被装饰类可以独立发展,不会相互耦合,装饰者模式是继承的一个替代模式,装饰者模式可以动态扩展一个实现类的功能

  • 使用场景
    • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
    • 不能采用继承的情况主要有两类:
      • 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得紫烈数目呈爆炸性增长
      • 第二类是因为类定义不能继承(如final类)
    • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
    • 当对象的功能要求可以动态第添加,也可以再动态地撤销时

桥接模式

  • 现有一个需求,需要创建不同的图形,并且每个图形都有可能会有不同的颜色。我们可以利用继承的方式来设计类的关系

  • 可以发现有很多的类,加入我们再增加一个形状或增加一种颜色,就需要创建更多的类
  • 在一个有多重可能会变化的维度的系统中,用继承方式会造成类爆炸,扩展起来不灵活。每次在一个维度上新增一个具体实现都要增加多个子类。为了更加灵活的设计系统,我们此时可以考虑使用桥接模式

  • 定义
    • 将抽象与现实分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变纬度的耦合度

  • 结构
    • 抽象化(Abstraction)角色:定义一个抽象类,并包含一个对实现化对象的引用
    • 扩展抽象化(Refind Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法
    • 具体化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用
    • 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现

  • 案例(视频播放器)
  • 需要开发一个跨平台视频播放器,可以在不同操作平台(如windows、Mac、Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB、AVI、WMV等。该播放器包含了两个纬度,适合使用桥接模式

  • 视频文件
//视频文件
public interface VideoFile {
    void decode(String fileName);
}
  • avi文件
//avi文件
public class AVIFile implements VideoFile{
    @Override
    public void decode(String fileName) {
        System.out.println("avi视频文件:"+fileName);
    }
}
  • rmvb文件
//rmvb文件
public class RMVBFile implements VideoFile {
    @Override
    public void decode(String fileName) {
        System.out.println("rmvb视频文件:"+fileName);
    }
}
  • 操作系统
//操作系统
public abstract class OperatingSystem {
    protected VideoFile videoFile;

    public OperatingSystem(VideoFile videoFile){
        this.videoFile = videoFile;
    }

    public abstract void play(String fileName);
}
  • Windows版本
//Windows版本
public class Windows extends OperatingSystem{

    public Windows(VideoFile videoFile) {
        super(videoFile);
    }

    @Override
    public void play(String fileName) {
       videoFile.decode(fileName);
    }
}
  • Mac版本
//Mac版本
public class Mac extends OperatingSystem{
    public Mac(VideoFile videoFile) {
        super(videoFile);
    }

    @Override
    public void play(String fileName) {
        videoFile.decode(fileName);
    }
}
  • 测试
//测试类
public class Client {
    public static void main(String[] args) {
       OperatingSystem os = new Windows(new AVIFile());
       os.play("名侦探柯南");  //avi视频文件:名侦探柯南
    }
}
  • 桥接模式提高了系统的可扩展性,在两个纬度中任意扩展一个纬度,都不需要修改原有系统。
  • 如:现在还有一个视频文件类型wmv,我们只需要再定义一个类实现VideoFile接口即可,其他类不需要发生变化

  • 使用场景
    • 当一个类存在两个独立变化的纬度,且这两个纬度都需要进行扩展时
    • 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时
    • 当一个系统需要在构件的抽象化角色和具体角色之间增加更多的灵活性时。避免在两个层次之间简历静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系

外观模式

  • 又名门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更多容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性

  • 主要角色
    • 外观角色:为多个子系统对外提供一个共同的接口
    • 子系统角色:实现系统的部分功能,客户可以通过外观角色访问它

  • 案例
    • 每次都需要打开灯、打开电视、打开空间;睡觉时关闭灯、关闭电视、关闭空调;操作起来都比较麻烦。所有买了只能音响,可以通过语音直接控制这些智能家电开启和关闭。

//灯类
public class Light {
    public void on(){
        System.out.println("打开了灯");
    }

    public void off(){
        System.out.println("关闭了灯");
    }
}
//电视类
public class TV {
    public void on(){
        System.out.println("打开了电视");
    }

    public void off(){
        System.out.println("关闭了电视");
    }
}
//空调类
public class AirCondition {
    public void on(){
        System.out.println("打开了空调");
    }

    public void off(){
        System.out.println("关闭了空调");
    }
}
//外观对象
public class SmartAppliancesFacade {
    private Light light;
    private TV tv;
    private AirCondition airCondition;

    public SmartAppliancesFacade(){
        light = new Light();
        tv = new TV();
        airCondition = new AirCondition();
    }

    public void say(String message){
        if (message.contains("打开")){
            on();
        }else if (message.contains("关闭")){
            off();
        }
    }

    private void on(){
        light.on();
        tv.on();
        airCondition.on();
    }

    private void off(){
        light.off();
        tv.off();
        airCondition.off();
    }
}
//测试
public class Client {
    public static void main(String[] args) {
        //创建外观对象
        SmartAppliancesFacade facade = new SmartAppliancesFacade();
        facade.say("打开");
        facade.say("关闭");
    }
}
posted @ 2022-10-31 22:31  youmo~  阅读(75)  评论(0编辑  收藏  举报