设计模式/设计模式七大原则

设计模式的目的

在进行软件开发时,为了让程序的实现**高内聚,低耦合,以及提高程序的维护性、扩展性、重用性、灵活性等**。

设计模式的本质就是**提高软件的维护性、通用性和扩展性,并降低软件的复杂度**。

核心思想:

  • 找出应用中可能需要变化之处,把他们独立出来,不要和那些不会变化的代码混在一起
  • 针对接口编程,而不是针对实现编程
  • 为了交互对象之间的松耦合设计而努力

设计模式七大原则

单一职责原则

单一职责原则(SRP),就一个类而言,应该仅有一个引起它变化的原因。

如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的功能。这种耦合关系会让系统变得很脆弱,当系统发生变化时,设计可能会遭到意想不到的破坏。

软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。

如果你能够想到多以一个的动机去改变一个类,那么这个类就具有多于一个的职责。

对类来说的,即一个类应该只负责一项职责。

  • 降低类的复杂度,一个类只负责一项职责
  • 提高类的可读性,可维护性
  • 降低变更引起的风险
  • 通常情况下,我们应该遵守单一职责原则,只有逻辑足够简单,才可以在代码级别违反单一职责原则;只有类中方法数量足够少,可以==在方法级别==保持单一职责原则。

接口隔离原则

客户端不应该依赖它不需要得接口,即一个类对另一个类的依赖应该建立在最小的接口上。

需求:

A通过Interface1依赖类B,但是只需要使用到1,2,3方法

C通过Interface1依赖类D,但是只需要使用到1,4,5方法

将Interface1进行==拆分成几个独立的接口==,A和C分别于与他们需要的接口建立关系。

在这里插入图片描述

依赖倒转原则

  • 高层模块不应该依赖低层模块,二者都应该依赖其抽象

  • 抽象不应该依赖细节,细节应该依赖抽象

  • 依赖倒转的中心思想是面向接口编程,不要面对实现编程

  • 相对于细节的多变性,抽象的东西要更加稳定。以抽象为基础的架构要比以细节为基础的架构更加稳定。在Java中,抽象指的是接口和抽象类,细节就是具体的实现类。

  • 使用接口和抽象类的目的就是制定好规范,而且不涉及任何具体的操作,把展现细节的任务交给他们的实现类。

代码实现:

interface IReceiver {
    String getInfo();
}

class Email implements IReceiver {
    public String getInfo() {
        return "Email";
    }
}

class Wechat implements IReceiver {
    public String getInfo() {
        return "Wechat";
    }
}

public class Person {
    public void receiver(IReceiver receiver) {
        System.out.println(receiver.getInfo());
    }
}

依赖关系传递的三种方式:

  1. 接口传递

    interface IOpenAndClose {
        void open(ITV tv);
    }
    
    interface ITV {
        void play();
    }
    
    class ChangHong implements ITV {
        void play() {
            System.out.println("ChangHong");
        }
    }
    
    public class OpenAndClose implements IOpenAndClose {
        public void open(ITV tv) {
            tv.play();
        }
    }
    
  2. 构造方法传递

    interface IOpenAndClose {
        void open();
    }
    
    interface ITV {
        void play();
    }
    
    class ChangHong implements ITV {
        void play() {
            System.out.println("ChangHong");
        }
    }
    
    public class OpenAndClose implements IOpenAndClose {
        public ITV tv;
        
        public OpenAndClose(ITV tv) {
            this.tv = tv;
        }
        
        // 这种方式在open方法中不需要传入tv,tv设置成了类的属性
        public void open() {
            this.tv.play();
        }
    }
    
  3. setter方法传递

    interface IOpenAndClose {
        void open();
        
        // 接口中多了一个setter方法
        void setter(ITV tv);
    }
    
    interface ITV {
        void play();
    }
    
    class ChangHong implements ITV {
        void play() {
            System.out.println("ChangHong");
        }
    }
    
    public class OpenAndClose implements IOpenAndClose {
        public ITV tv;
        
        public void setter(ITV tv) {
            this.tv = tv;
        }
        
        public void open() {
            this.tv.play();
        }
    }
    

注意事项:

  1. 底层模块尽量要有接口或者抽象类,或者两者都有,程序稳定性更好
  2. 变量的声明类型尽量要是接口或者抽象类,这样我们在变量引用和实际对象间,就会存在一个缓冲层,有利于程序的扩展和优化。
  3. 继承时应该遵守里氏替换原则。

里氏替换原则

继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承时会给程序带来入侵性,程序的可移植性降低了,增加了对象之间的耦合性。在实际编程中,我们常常会通过重写父类的方法来实现新的功能,这样虽然写起来简单,但是会使得整个继承体系的复用性非常差,特别时是在运行多态比较频繁的时候。为了避免这些情况,在使用继承时,应该尽量遵守**里氏替换原则**。

里氏替换原则:

  • 如果对于每个类型为T1的对象o1,都有类型为T2的对象o2,使得T1定义的所有程序P,在所有对象o1都替换成o2时,程序P的行为没有发生变化,那么类型T2就是类型T1的子类。换句话说,所有引用基类的地方必须能透明的使用其子类的对象。
  • 在使用继承时,遵循里氏替换原则,在**子类中尽量不要重写父类的方法。**
  • 继承实际上让两个类的耦合增强了,如果子类需要修改父类的方法,在适当情况下,可以通过聚合、组合、依赖来解决问题。

在这里插入图片描述

解决方法:

将原来的父类和子类继承一个更加基础的基类,原有的基础关系去掉,采用依赖、聚合、组合等关系来代替。

如果B类需要用到A类中的方法,不必继承A类,只需要通过组合关系,在B类中将A类作为一个属性,然后调用A类的方法。

在这里插入图片描述

开闭原则

  • 开闭原则是编程中**最基础最重要**的设计原则。
  • 模块和函数应该**对扩展开放(对提供方),对修改关闭(对使用方)用抽象构建框架,用实现扩展细节**。
  • 当软件需求发生变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现。
  • 编程中遵循其他原则,以及使用设计模式的目的就是遵循开闭原则。

以下代码在新增时,会需要修改GraphicEditor中的drawShape方法,并且需要在GraphicEditor添加新的方法。

public class GraphicEditor {
	public void drawShape(Shape s) {
        if (s.m_type == 1) {
            drawRectangle(s);
        } else if (s.m_type == 2) {
            drawCircle(s);
        }
    }
    
    public void drawRectangle(Shape s) {
        System.out.printle("Rectangle");
    }
    
    public void drawCircle(Shape s) {
        System.out.printle("Circle");
    }
}

class Shape {
    int m_type;
}

class Rectangle extends Shape {
    Rectangle() {
        super.m_type = 1;
    }
}

class Circle extends Shape {
    Circle() {
        super.m_type = 2;
    }
}

下面的代码在需要新增功能时,只需要继承相关的抽象类,实现(注意是实现,不是重写)相关的方法即可,不需要修改抽象类。能够实现使用方不需要修改原本的代码,提供方实现扩展即可。

public class GraphicEditor {
	public void drawShape(Shape s) {
        s.draw();
    }    
}

abstract class Shape {
    int m_type;
    
    // 绘图抽象方法
    public abstract void draw();
}

class Rectangle extends Shape {
    Rectangle() {
        super.m_type = 1;
    }
    
    @Override
    public void draw() {
        System.out.println("Rectangle");
    }
}

class Circle extends Shape {
    Circle() {
        super.m_type = 2;
    }
    
    @Override
    public void draw() {
        System.out.printle("Circle");
    }
}

迪米特法则

迪米特法则的核心是**降低类之间的耦合**。

由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类之间(对象之间)的耦合关系,并不是完全没有依赖。

基本介绍:

  • 一个对象应该对其他对象保持**最少的了解**。
  • 迪米特法则又叫**最少知道原则**,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管有多复杂,都尽量将逻辑封装在类的内部。对外除了提供一个public方法,不再透露任何信息。
  • 迪米特法则还有一个更简单的定义:只与直接朋友进行通信
    • 直接朋友:每个类都会与其他对象有耦合关系,只要两个对象之间有耦合关系,就被称为朋友关系。耦合的方式有很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值的类为直接朋友,而出现局部变量的类不是直接朋友。也就是说,陌生类最好不要以局部变量的形式出现在类的内部。
class Emp {
    private String id;
    
    public void setId(String id) {
        this.id = id;
    }
    public String getId() {
        return id;
    }
}

class CollegeEmp {
    private String id;
    
    public void setId(String id) {
        this.id = id;
    }
    public String getId() {
        return id;
    }
}

class CollegeManager {
    public List<CollegeEmp> getAllEmp() {
        List<CollegeEmp> list = new List<CollegeEmp>();
        for (int i = 0; i < 10; i++) {
            CollegeEmp emp = new CollegeEmp();
            emp.setId(i);
            list.add(emp);
        }
        return list;
    }
}

// Emp和CollegeManager是直接朋友
// CollegeEmp不是直接朋友,违反了迪米特法则
class SchoolManager {
    public List<Emp> getAllEmp() {
        List<Emp> list = new List<Emp>();
        for (int i = 0; i < 10; i++) {
            Emp emp = new Emp();
            emp.setId(i);
            list.add(emp);
        }
        return list;
    }
    
    public void printAllEmp(CollegeManager sub) {
        List<CollegeEmp> list1 = sub.getAllEmp();
        System.out.println("-----分公司员工-----");
        for (CollegeEmp e : list1) {
            System.out.println(e.getId());
        }
        List<Emp> list2 = this.getAllEmp();
        System.out.println("-----分公司员工-----");
        for (Emp e : list2) {
            System.out.println(e.getId());
        }
    }
}

public static vooid main(String[] args) {
    SchoolManager schoolManager = new SchoolManager();
    schoolManager.printAllEmp(new CollegeManager());
}

上述代码中,SchoolManager类中只有Emp和CollegeManager是直接朋友,CollegeEmp不是直接朋友,所以违反了迪米特法则,不应该在SchoolManager类进行过多的操作,需要将对CollegeEmp类进行操作的代码直接写在CollegeEmp类中。

class Emp {
    private String id;
    
    public void setId(String id) {
        this.id = id;
    }
    public String getId() {
        return id;
    }
}

class CollegeEmp {
    private String id;
    
    public void setId(String id) {
        this.id = id;
    }
    public String getId() {
        return id;
    }
}

class CollegeManager {
    public List<CollegeEmp> getAllEmp() {
        List<CollegeEmp> list = new List<CollegeEmp>();
        for (int i = 0; i < 10; i++) {
            CollegeEmp emp = new CollegeEmp();
            emp.setId(i);
            list.add(emp);
        }
        return list;
    }
    
    public void printAllEmp() {
        List<CollegeEmp> list1 = getAllEmp();
        System.out.println("-----分公司员工-----");
        for (CollegeEmp e : list1) {
            System.out.println(e.getId());
        }
    }
}

// Emp和CollegeManager是直接朋友
// CollegeEmp不是直接朋友,违反了迪米特法则
class SchoolManager {
    public List<Emp> getAllEmp() {
        List<Emp> list = new List<Emp>();
        for (int i = 0; i < 10; i++) {
            Emp emp = new Emp();
            emp.setId(i);
            list.add(emp);
        }
        return list;
    }
    
    public void printAllEmp(CollegeManager sub) {
//        List<CollegeEmp> list1 = sub.getAllEmp();
//        System.out.println("-----分公司员工-----");
//        for (CollegeEmp e : list1) {
//            System.out.println(e.getId());
//        }
        
        sub.printAllEmp();
        
        List<Emp> list2 = this.getAllEmp();
        System.out.println("-----分公司员工-----");
        for (Emp e : list2) {
            System.out.println(e.getId());
        }
    }
}

public static vooid main(String[] args) {
    SchoolManager schoolManager = new SchoolManager();
    schoolManager.printAllEmp(new CollegeManager());
}

合成复用原则

尽量使用**合成/聚合**的方式,而不使用继承。

在这里插入图片描述

B类只需要调用A类的方法,如果使用继承,很增大A和B之间的耦合性。

解决办法:

方法一:在这里插入图片描述
使用依赖:B类中传入A类的对象实例,B依赖A

方法二:在这里插入图片描述
使用聚合:在B类中添加A类的属性,把A聚合到B中

方法三:在这里插入图片描述
使用组合:在B中创建A的对象,把A组合到B中

23种设计模式

设计模式分为**三种类型,共23种**

  • 创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式
  • 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
  • 行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式

单例模式

采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个过去类对象实例的方法

比如Hibernate的SessionFactory,它充当数据存储元的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般来说,整个程序只需要一个SessionFactory就够了,所以使用单例模式。

单例模式创建的八种方式:

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. 懒汉式(同步代码块)
  6. 双重检查
  7. 静态内部类
  8. 枚举
饿汉式(静态常量)
  1. 构造器私有化(防止外界new)
  2. 类的内部创建对象
  3. 向外暴露一个静态的公共方法获取实例(getInstance)
public class Singleton {
    
    private final static Singleton instance = new Singleton();
    
    private Singleton() {
        
    }
    
    public static Singleton getInstance() {
        return instance;
    }
    
}

优缺点:

  • 优点:这种方法书写比较简单,在类装载时完成实例的创建,避免了线程同步问题
  • 缺点:在类转载时完成实例化,没有达到Lazy Loading的效果。如果从始至终都没有使用到该类,会在**造成内存的浪费**。
  • 这种基于classloader的机制避免了多线程同步的问题(因为在**类加载时便创建好一个静态的对象,之后便不会再改变)。但是导致类加载的原因有很多种,因此不能确定其他方式(或者其他静态方法)导致类加载,这就导致了初始化instance的时候达不到懒加载的效果**。
  • 结论:可能造成资源浪费
饿汉式(静态代码块)
public class Singleton {
    private static Singleton instance;
    
    static {
        instance = new Singleton();
    }
    
    private Sinleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}

这种方式的优缺点和上面的类似。

结论:可能造成资源浪费

懒汉式(线程不安全)
public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return singleton;
    }
}

优缺点:

  • 优点:起到了Lazy Loading的效果,但是只能在单线程中使用。
  • 缺点:如果在多线程中,当一个线程进入if判断语句,但是还没有将对象实例化;同时另一个线程也进入了if判断语句,这个时候便会破坏单例模式。所以在多线程中不能使用这种方式。
  • 结论:在真实生产中不可以使用这种方法,只能用于单线程,因为存在线程安全问题。
懒汉式(线程安全,同步方法)
public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

优缺点:

  • 优点:解决了线程安全问题。
  • 缺点:效率太低。每个线程想要获取了类实例的时候,都需要进行同步。然后这个方法只要执行一次就行了,之后可以直接返回instance实例。方法进行同步效率太低了。
  • 结论:效率过低,不推荐使用。
懒汉式(同步代码块)
public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}

和第三种方法错误相同,解决不了任何问题。

结论:无法使用。

双重检查
public class Singleton {
    // 此处的volatile是为了防止指令重排序
    private static volatile Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    singleton = new Singleton();
                }
            }
        }
        return instance;
    }
}

这里必须加volalite的原因:

new 对象的过程分为三步:

  1. 分配空间
  2. 初始化对象
  3. 指向对象内存地址。

2和3可能被编译器自动重排,导致判断非空但是实际拿的对象还未完成初始化。

优缺点:

  • 优点:Double-check是在多线程开发中经常使用的,进行了两次if (instance == null) 的判断,第一次是提高效率,第二次是保证线程安全。
  • 结论:保证了线程安全,实现了延迟加载,效率较高;推荐使用。
静态内部类
public class Singleton {
    private 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 enum Singleton {
    INSTANCE;
    
    public void method() {
        
    }
}

优缺点:

  • 优点:借助JDK1.5中添加的枚举来实现单例模式,不仅能避免多线程同步的问题,而且**还能防止反序列化重新创建新的对象**。
  • 结论:推荐使用,而且是Effective Java作者推荐使用。
单例模式总结

Java中使用到的单例模式:

java.lang.Runtime使用饿汉式单例模式

在这里插入图片描述

java.util.logging中多次使用了单例模式(饿汉式、双重检测模式)

在这里插入图片描述

注意事项和细节说明:

  1. 单例模式保证了系统内存中该对象实例只存在一份,节省了系统资源,对于一些频繁需要创建销毁的,使用单例模式可以提高系统的性能。
  2. 当想使用单例模式时,记住要使用相应获取对象的方法,不要使用new。

单例模式使用场景:

  • 需要频繁创建销毁的对象
  • 创建对象时耗时过多或者使用资源较多(即重量级对象),但是又经常用到
  • 工具类对象
  • 频繁访问数据库或者文件的对象(比如数据源、session工厂等)

简单工厂模式

public class OperationFactory {
    public static Operation createOperate(String operate) {
        Operation oper = null;
        switch (operation) {
            case "+":
                oper = new OperationAdd();
                break;
            case "-":
                oper = new OperationSub();
                break;
            case "*":
                oper = new OperationMul();
                break;
            case "/":
                oper = new OperationDiv();
                break;
            default:
                break;
        }
        return oper;
    }
}

工厂方法模式

工厂方法模式是简单工厂的进一步抽象和推广。

优点:使用了多态性,保持了简单工厂模式的优点,而且克服了它的缺点。

缺点:但是会增加额外的开发量。

public interface IFactory {
    LeiFeng createLeiFeng();
}

class UndergraduateFactory implements IFactory {
    public Leifeng createLeifeng() {
        return new Undergraduate();
    }
}

class VolunteerFactory implements IFactory {
    public Leifeng createLeifeng() {
        return new Volunteer();
    }
}

/** 
 * 当需要换成“志愿者”时,只需要将UndergraduateFactory换成VolunteerFactory,
 * 其他的不需要改变;需要新增其他相关的也不需要修改服务层代码,只需要新增一个实现类即可;
 * 保证了对修改关闭,对扩展开放
 */
IFactory factory = new UndergraduateFactory();
LeiFeng leiFeng = factory.createLeiFeng();

未完待续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值