1、设计模式七大原则

设计模式体现了代码的耦合性, 内聚性以及可维护性,可扩展性,重用性,灵活性。

  • 1、代码重用性(即:相同功能的代码,不用多次编写)
  • 2、可读性(即:编程规范性,便于其他程序员的阅读和理解)
  • 3、可扩展性(即:当需要增加新的功能时,非常的方便,称为可维护)
  • 4、可靠性(即:当我们增加新的功能后,对原来的功能没有影响)
  • 5、使程序呈现高内聚,低耦合的特性

Scott Mayers在其巨著《Effective C++》说过: C++老手和C++新手的区别就是前者手背上有很多伤疤。

  • 同样是面向对象,当然也可指 Java

一、单一职责原则(Single responsibility)

单一职责原则注意事项和细节:

  • 1、降低类的复杂度,一个类只负责一项职责;
  • 2、提高类的可读性,可维护性;
  • 3、降低变更引起的风险;
  • 4、通常情况下,应当遵守单一职责原则, 只有逻辑足够简单,才可以在方法级违反单一职责原则
/**
 * 只有类中方法数量足够少,可以在方法级别保持单一职责原则
 */
public class Singleresponsibility {
    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
        vehicle.run("跑跑卡丁车");
        vehicle.fly("直升飞机");
    }
}

//交通工具类
// 逻辑简单,方法级别实现单一职责
// 逻辑复杂,分类实现单一职责
class Vehicle{
    public void run(String vehicle){
        System.out.println(vehicle+"在陆地上跑");
    }
    public void fly(String vehicle){
        System.out.println(vehicle+"在天空上飞");
    }
}

二、接口隔离原则(Interface Segregation)

  • 1、类A通过接口 Interface1、2 依赖类B,类C通过接口 Interface1、3 依赖类D,如果接口 Interface 对于 类A 和 类C 来说不是最小接口,那么 类B 和 类D 必须去实现他们不需要的方法。
  • 2、将接口 Interface 拆分为独立的几个接口,类A 和 类C 分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
  • 3、接口 Interface 中出现的方法,根据实际情祝拆分为三个接口。

image-20200505134625867

public class Segregation {
    public static void main(String[] args) {
        A a = new A();
        a.depent1(new B());//A依赖B
        a.depent2(new B());
        a.depent3(new B());
        C c = new C();
        c.depent1(new D());//C依赖D
        c.depent2(new D());
        c.depent3(new D());
    }
}

interface interface1{
    void operation1();
}

interface interface2{
    void operation2();
    void operation3();
}

interface interface3{
    void operation4();
    void operation5();
}

class B implements interface1,interface2{

    @Override
    public void operation1() {
        System.out.println("b实现了operation1");
    }

    @Override
    public void operation2() {
        System.out.println("b实现了operation2");
    }

    @Override
    public void operation3() {
        System.out.println("b实现了operation3");
    }
}

class D implements interface1,interface3{
    @Override
    public void operation1() {
        System.out.println("D实现了operation1");
    }

    @Override
    public void operation4() {
        System.out.println("D实现了operation4");
    }

    @Override
    public void operation5() {
        System.out.println("D实现了operation5");
    }
}

class A{
    void depent1(interface1 i){
        i.operation1();
    }
    void depent2(interface2 i){
        i.operation2();
    }
    void depent3(interface2 i){
        i.operation3();
    }
}
class C{
    void depent1(interface1 i){
        i.operation1();
    }
    void depent2(interface3 i){
        i.operation4();
    }
    void depent3(interface3 i){
        i.operation5();
    }

三、依赖倒转原则(Dependence Inversion)

  • 1、高层模块不应该依赖低层模块,二者都应该依赖其抽象(缓冲层);

  • 2、抽象不应该依赖细节,细节应该依赖抽象;

  • 3、依赖倒转(倒置)的中心思想是面向接口编程;

  • 4、依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中, 抽象指的是接口或抽象类,细节就是具体的实现类;

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

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

    • 接口传递(依赖)
    • 构造方法传递(依赖)
    • setter方式传递(聚合)
//依赖倒转原则
public class DependenceInversion {
    public static void main(String[] args) {
        persion persion = new persion();
        persion.receve(new Email());
        persion.receve(new Wechat());
    }
}

interface IRecever{
    String getInfo();
}

class Email implements IRecever{

    @Override
    public String getInfo() {
        return "email:helloworld";
    }
}
class Wechat implements IRecever{

    @Override
    public String getInfo() {
        return "Wechat:helloworld";
    }
}



//模拟人收到消息
class persion{
    //接受类型为接口
    public void receve(IRecever recever){
        String info = recever.getInfo();
        System.out.println(info);
    }
}

四、里氏替换原则(Liskov Substitution)

  • 1、里氏替换原则(Liskov Substitution Principle)在1988年,由麻省理工学院一位姓里的女士提出;

  • 2、如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象;

  • 3、在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法

  • 4、继承实际上让两个类耦合性增强了,给程序带来侵入性。在适当的情况下,可以通过聚合,组合,依赖来解决问题;

  • 5、继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。

    public class LiskovSubstitution {
        public static void main(String[] args) {
            A a = new A();
            System.out.println("2-1=" + a.func1(2, 1));
    
            B b = new B();
            System.out.println("2+1=" + b.func1(2, 1));
            System.out.println("2+1+9=" + b.func2(2, 1));
            System.out.println("B类使用A类方法:2-1=" + b.func3(2, 1));
        }
    }
    
    class Base {
        //把基础方法和成员抽取成基类
        public int func1(int num1, int num2) {
            return num1 - num2;
        }
    }
    
    class A extends Base {
    
    //    public int func1(int num1, int num2) {
    //        return num1 - num2;
    //    }
    }
    
    class B extends Base {
    
          // TODO 类 B `无意` 重写了父类 A 方法,造成原有方法发生改变。
    //    @Override
    //    public int func1(int num1, int num2) {
    //        return num1 + num2;
    //    }
    
        @Override
        public int func1(int num1, int num2) {
            return num1 + num2;
        }
    
        public int func2(int num1, int num2) {
            return func1(num1, num2) + 9;
        }
    
        private A a = new A();//组合
    
        //使用 A 方法
        public int func3(int num1, int num2) {
            return this.a.func1(num1, num2);
        }
    }
    

五、开闭原则 OCP(Open Closed

  • 1、开闭原则(Open Closed Principle) 是编程中最基础、最重要的设计原则;
  • 2、一个软件实体,比如类,模块和函数应该对提供方扩展开放,对使用方修改关闭。用抽象构建框架,用实现扩展细节;
  • 3、当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化;
  • 4、编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。
//开闭原则
public class Ocp {
    public static void main(String[] args) {
        Use use = new Use();
        use.drawShape(new Triangle());
        use.drawShape(new Circle());
        use.drawShape(new OtherGraphics());
    }
}


class Use{
    public void drawShape(Shape shape){
        shape.draw();
    }
}

abstract class  Shape{
    public abstract void draw();
}

class Triangle extends Shape{
    @Override
    public void draw() {
        System.out.println("绘制三角形");
    }
}

class  Circle extends  Shape{
    @Override
    public void draw() {
        System.out.println("绘制圆形");
    }
}
class OtherGraphics extends Shape {

    @Override
    public void draw() {
        System.out.println("子类实现具体功能:任何形状");
    }
}

六、迪米特法则(Demeter)

  • 1、一个对象应该对其他对象保持最少的了解(最少知道原则 LKP)

  • 2、类与类关系越密切,耦合度越大。要求降低类之间耦合,而不是完全解耦。

  • 3、迪米特法则(Demeter Principle),即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供public方法,不对外泄露任何信息。

  • 4、迪米特法则更简单的定义:只与直接的朋友通信

  • 5、直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合 等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。

    class A{
    	B b;//全局变量 - 直接朋友
    	public B m1(){} //方法返回值 - 直接朋友
    	public void m2(B b){}//方法入参 - 直接朋友
    	public void m3(){
    		B b1 = new B();// 局部变量 非直接朋友
    	}
    }
    
    public class Demeter {
        public static void main(String[] args) {
            SchoolManager schoolManager = new SchoolManager();
            schoolManager.printAllEmployee(new CollegeManager());
        }
    }
    
    //学院员工类
    class CollegeEmployee {
        private String id;
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    }
    
    //管理学院员工的管理类:
    class CollegeManager {
        //返回学院的所有员工 //TODO CollegeEmployee 直接朋友
        public List<CollegeEmployee> getAllEmployee() {
            List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
            for (int i = 0; i < 10; i++) { //这里我们增加了10 个员工到list ,
                CollegeEmployee emp = new CollegeEmployee();
                emp.setId("学院员工id " + i);
                list.add(emp);
            }
            return list;
        }
    
        public void printCollegeEmployee() {
            List<CollegeEmployee> list1 = this.getAllEmployee();
            System.out.println("---学院员工----");
            for (CollegeEmployee e : list1) {
                System.out.println(e.getId());
            }
        }
    }
    
    //学校总部员工类
    class SchoolEmployee {
        private String id;
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    }
    
    //学校管理类
    //TODO 直接朋友 Employee CollegeManager
    class SchoolManager {
        //返回学校总部的员工
        public List<SchoolEmployee> getAllEmployee() {
            List<SchoolEmployee> list = new ArrayList<SchoolEmployee>();
            for (int i = 0; i < 5; i++) { //这里我们增加了5个员工到list
                SchoolEmployee emp = new SchoolEmployee();
                emp.setId("学校总部员工id= " + i);
                list.add(emp);
            }
            return list;
        }
    
        //该方法完成输出学校总部和学院员工信息(id)
        void printAllEmployee(CollegeManager sub) {
            //获取到学院员工
            //TODO 非直接朋友 CollegeEmployee  应该提取到  CollegeManager
    //        List<CollegeEmployee> list1 = sub.getAllEmployee();
    //        System.out.println("---学院员工----");
    //        for (CollegeEmployee e : list1) {
    //            System.out.println(e.getId());
    //        }
            sub.printCollegeEmployee();//只提供方法,不把具体实现放在其他类里面。
    
            //获取到学校总部员工
            List<SchoolEmployee> list2 = this.getAllEmployee();
            System.out.println("------学校总部员工------");
            for (SchoolEmployee e : list2) {
                System.out.println(e.getId());
            }
        }
    }
    

七、合成复用原则(Composite Reuse)

合成复用原则 尽量使用组合/聚合的方式,而不是使用继承。

  • 1、找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

  • 2、针对接口编程,而不是针对实现编程。

  • 3、为了交互对象之间的松耦合设计而努力

  • public class CompositeReuse {
        public static void main(String[] args) {
            System.out.println("------依赖------");
            B b = new B();
            b.Operation1(new A());
    
            System.out.println("------聚合------");
            b.setA(new A());
            b.Operation2();
    
            System.out.println("------组合------");
            b.Operation3();
        }
    }
    
    class A {
        void Operation1() {
            System.out.println("A Operation1");
        }
    
        void Operation2() {
            System.out.println("A Operation2");
        }
    
        void Operation3() {
            System.out.println("A Operation3");
        }
    }
    
    //如果只是需要用到 A类的方法,尽量不要使用继承。而是使用,依赖,聚合,组合的方式
    class B {
        void Operation1(A a) {//TODO 依赖
            a.Operation1();
            a.Operation2();
            a.Operation3();
        }
    
        //==============================================================
        A a;
        public void setA(A a) {
            this.a = a;
        }
    
        void Operation2() {//TODO 聚合
            a.Operation1();
            a.Operation2();
            a.Operation3();
        }
    
        //==============================================================
        A a1 = new A();
    
        void Operation3() {//TODO 组合
            a1.Operation1();
            a1.Operation2();
            a1.Operation3();
        }
    }
    

    总结一下,依赖是传入参数,聚合是通过set注入,组合是再类里new一个实例。

2、UML

IDEA PlantUML表示类与类之间的关系的符号

@startuml

Class1 <|-- ClassA:泛化
Class2 <-- ClassB:关联
Class3 *-- ClassC:组合
Class4 o-- ClassD:聚合
Class5 <|.. ClassE:实现
Class6 <.. ClassF:依赖

@enduml

image-20200505172459261

1、依赖(Dependence)

只要是在类中用到了对方,那么他们之间就存在依赖关系。如果没有对方,连编绎都通过不了。

  • 类中用到了对方;

  • 类的成员属性;

  • 方法的返回类型;

  • 方法接收的参数类型;

  • 方法中使用到

  • image-20200505174608185

    /**
     * 类中用到了对方;
     * 类的成员属性;
     * 方法的返回类型;
     * 方法接收的参数类型;
     * 方法中使用到;
     */
    public class Dependence {
        A a;//TODO 类的成员属性
    
        public A save(B b) {//TODO 方法接收的参数类型
            //TODO 方法的返回类型
            System.out.println("");
            A a = new A();//TODO 方法中使用到
            return a;
        }
    }
    
    class A {}
    
    class B {}
    

2、继承(泛化 Generalization)

image-20200505174702414

public class Generalization extends Base {

    @Override
    public void get(Object oId) {

    }

    @Override
    public void put(Object oName) {

    }
}

abstract class Base {
    abstract public void get(Object oId);

    abstract public void put(Object oName);
}

3、实现(Realization)

实现关系实际上就是 A类 实现 B接口,依赖关系的特例。

image-20200505174744892

public class Implementation implements Base {
    @Override
    public void init() {
        System.out.println("init");
    }
}

interface Base {
    void init();
}

4、关联(Association)

类与类之间的关系,依赖关系的特例。

关联具有导航性:即双向关系或单向关系。

image-20200505174827128

public class Person {
    private IDCard idCard;
}

class IDCard {
    //private Person person;
}

5、聚合(Aggregation)

表示的是整体和部分的关系,整体与部分可以分开,关联关系的特例。

聚合关系是关联关系的特例,所以他具有关联的导航性与多重性。

image-20200505174903689

public class Computer {
    private Mouse mouse;
    private Keyboard keyboard;

    public void setMouse(Mouse mouse) {
        this.mouse = mouse;
    }

    public void setKeyboard(Keyboard keyboard) {
        this.keyboard = keyboard;
    }
}

class Mouse {}

class Keyboard {}

6、组合(Composite)

整体与部分的关系,但是整体与部分不可以分开,关联关系的特例。

级联删除就是组合关系。

image-20200505174939312

public class Computer {
   private CPU cpu = new CPU();
   private SSD ssd = new SSD();
}

class CPU {}

class SSD {}

3、单例模式

单例设计模式 —> 创建型模式

单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。
通过单例模式可以保证系统中,应用该模式的类只有一个实例。即一个类只有一个对象实例。

在java语言中,单例带来了两大好处:

(1)对于频繁使用的对象(数据源、Session工厂),可以省略创建对象所花费的时间,这对于重量级的对象而言,是非常可观的一笔系统开销。
(2)由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。

具体实现

需要:
(1)将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
(2)在该类内部产生一个唯一的实例化对象,并且将其封装为 private static 类型。
(3)定义一个静态方法返回这个唯一对象。

实现一:饿汉式 / 静态常量

  • 立即加载就是使用类的时候已经将对象创建完毕(不管以后会不会使用到该实例化对象,先创建了)。常见的实现办法就是直接new实例化。
/**
 * 懒汉单例
 */
class Singleton{
    //构造器私有化
    private Singleton(){
    }
    //类加载时实例化
    private final static Singleton instance = new Singleton();
    //对外提供静态方法
    public static Singleton getInstance(){
        return instance;
    }
}

“饿汉模式”的优缺点:

  • 优点:实现起来简单,没有多线程同步问题。
  • 缺点:当类 Singleton 被加载的时候,会初始化 static 的 instance,静态变量被创建并分配内存空间,从这以后,这个 static 的 instance 对象便一直占着这段内存,可能造成内存浪费(即便你还没有用到这个实例)。当类被卸载时,静态变量被摧毁,并释放所占有内存,在某些特定条件下会耗费内存。
  • 如果方法内有其他 static 方法,调用该方法此类也加载初始

实现二:饿汉式 / 静态代码块

//懒汉静态代码块
class Singleton1{
    private static Singleton1 instance;
    private Singleton1(){
    }
    static {
         instance = new Singleton1();
    }
    public static Singleton1 getInstance(){
        return instance;
    }
}

实现三:懒汉式 / 线程不安全

  • 延迟加载就是调用 getInstance() 方法时实例才被创建(先不急着实例化出对象,等要用的时候才创建出来)。常见的实现方法就是在 getInstance() 方法中进行new实例化。
public class LazyLoadingSingletonThreadUnSafe {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1==singleton2);//true
    }
}
//懒汉单例 线程不安全
class Singleton{
    //将自己实例化对象设置为一个属性,用static
    private static Singleton instance;
    //构造方法私有化
    private Singleton(){}

    //静态方法返回实例
    public static Singleton getInstance(){
        if (instance==null){
            //线程在这里被阻塞,则对象没别创建,不安全
            instance = new Singleton();
        }
        return instance;
    }
}

懒汉模式”的优缺点:

  • 优点:实现起来比较简单,当类 Singleton 被加载的时候,静态变量 static 的 instance 未被创建并分配内存空间,当 getInstance() 方法第一次被调用时,初始化instance变量,并分配内存,因此在某些特定条件下会节约了内存。
  • 缺点:在多线程环境中,这种实完现方法是全错误的,不能保证单例的状态。
  • 如果方法内有其他 static 方法,调用该方法此类不会加载初始化。

实现四:懒汉式 / 线程安全 Sync

  • 静态方法返回该实例,加 Synchronized 关键字实现同步。
class Singleton1{
    //将自己实例化对象设置为一个属性,用static
    private static Singleton1 instance;
    //构造方法私有化
    private Singleton1(){}

    //静态方法返回实例,方法加synchronized 关键字
    public static synchronized Singleton1 getInstance(){
        if (instance==null){
            instance = new Singleton1();
        }
        return instance;
    }
}

线程安全的“懒汉模式”(加锁)的优缺点:

  • 优点:在多线程情形下,保证了“懒汉模式”的线程安全。
  • 缺点:在多线程情形下,synchronized方法通常效率低,显然这不是最佳的实现方案。
  • 如果方法内有其他static方法,调用该方法此类不会加载初始化。

实现五:DCL双检查锁机制

(DCL:Double Checked Locking)

public class LazyLoadingSingletonDCL {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance==instance1);
    }
}


class Singleton {
    // 将自身实例化对象设置为一个属性,并用 volatile、static 修饰
    private volatile static Singleton instance;

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

    // 静态方法返回该实例
    public static Singleton getInstance() {
        // 第一次检查instance是否被实例化出来
        if (instance == null) {
            synchronized (Singleton.class) {
                // 某个线程取得了类锁,实例化对象前第二次检查 instance 是否已经被实例化
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

方法五算是单例模式的最佳实现方式。内存占用率高,效率高,线程安全,多线程操作原子性。

  • 如果方法内有其他 static 方法,调用该方法此类不会加载初始化。

实现六:静态内部类

class Singleton{
    private Singleton(){}

    public static Singleton getInstance(){
        return SingleHolder.INSTANCE;
    }
    //静态的内部类
    private static class SingleHolder{
        //加载 Singleton 类时并不会加载内部类
        private static final Singleton INSTANCE = new Singleton();
    }
}

第一次加载 Singleton 类时并不会加载内部类初始化 Instance,只有第一次调用 getInstance 方法时虚拟机加载 SingletonHolder 并初始化 Instance ,这样不仅能确保线程安全也能保证 Singleton类的唯一性,所以推荐使用静态内部类单例模式。

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }
	//TODO 私有化构造器
    private Runtime() {}