设计原则
开闭原则
- 主要指对扩展开放,对修改关闭。用抽象构建框架,用实现扩展细节。
- 优点:保持软件的稳定性,不影响原有业务逻辑代码。使代码更具有模块化,易于维护。提高开发效率。
- 来源:https://segmentfault.com/a/1190000021922108
里式替换原则
- 所有引用基类的地方必须能透明地使用其子类的对象
- 原则:子类必须实现父类的抽象方法,不得重写父类的非抽象方法
- 子类可以增加自己特有的方法
- 子类在实现父类的方法的时候,形参要比父类的参数更加宽松。
- 子类实现父类的方法的时候,出参要比父类更加严格。
依赖倒置原则
- 高层模块不应该直接依赖底层模块,二者应该依赖抽象,抽象不应该依赖细节,细节应该依赖抽象。
- 比如A类是接口,B类是实现,而C类要使用B类,那么C类不能直接使用B类,而是通过依赖A接口来实现,这样将使用谁的权交给了外界。C类更加的灵活
单一职业原则
- 一个类或者模块只负责完成一个职责。不要设计大而全的类,要设计粒度小、功能单一的类。单一职责原则是为了实现代码高内聚、低耦合,提高代码的复用性、可读性、可维护性。
接口隔离原则
- 尽量将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。和单一职责原则相比,他们都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想。两者不同在于单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。单一职责原则主要是约束类,它针对的是程序中的实现和细节。接口隔离原则主要是约束接口,主要针对抽象和程序整体框架的构建。
迪米特法则
- 如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用,目的是降低类之间的耦合度,提高模块的相对独立性。由于亲合度降低,从而提高了类的可复用率和系统的扩展性。
- 比如A类想调用B类,但是他们又没有直接联系,而通过中间的C类来调用B类。A->C->B这种调用关系
合成复用原则
- 要求在软件复用时,尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用集成关系来实现。采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,从而新对象可以调用已有对象的功能。
- 维持了类的封装性,因为成分对象的内部细节是新对象看不见的,这种复用称为黑箱复用。
- 新旧类之间的耦合度低,这种复用所需的依赖较少,新对象存储成分对象的唯一方法是通过成分对象的接口。
- 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。
设计模式
- 设计模式分为创建型、结构型、行为型。
- 创建型:用于对象的创建,提高代码的灵活性和复用性
设计模式 描述 工厂方法模式(Factory Method) 通过工厂类提供一个创建对象的接口,而不是直接 new 一个对象,提高代码的可扩展性。 抽象工厂模式(Abstract Factory) 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。 单例模式(Singleton) 确保一个类只有一个实例,并提供一个全局访问点。 建造者模式(Builder) 用于创建复杂对象,将对象的构建与表示分离,以便相同的构建过程可以创建不同的表示。 原型模式(Prototype) 通过复制(克隆)已有的实例来创建新对象,而不是通过实例化。 - 结构型模式:主要用于类与对象的组合,确保系统的结构更加灵活和高效。
设计模式 描述 适配器模式(Adapter) 让原本不兼容的接口能够协同工作,相当于“转换器”。 桥接模式(Bridge) 分离抽象部分和实现部分,使它们可以独立变化,提高可扩展性。 装饰器模式(Decorator) 通过动态地给对象增加额外的功能,而不会改变其结构(类似于 Java 的 IO 流)。 组合模式(Composite) 允许将对象组合成树形结构,以表示“整体-部分”关系,适用于树形结构数据。 外观模式(Facade) 提供一个统一的接口,用于访问子系统的一组接口,简化客户端的调用。 享元模式(Flyweight) 通过共享对象,减少内存占用,提高性能。 代理模式(Proxy) 通过代理对象控制对目标对象的访问,例如:静态代理、动态代理(JDK/CGLIB)。 - 行为型模式:主要用于对象之间的通信和职责分配,提高代码的可维护性和可扩展性。
设计模式 描述 策略模式(Strategy) 定义一系列算法,将每种算法封装起来,并使它们可以互换。 观察者模式(Observer) 允许对象间建立一对多的依赖关系,当一个对象状态变化时,所有依赖它的对象都会收到通知(如 监听器 机制)。 责任链模式(Chain of Responsibility) 将请求沿着处理链传递,直到某个对象处理请求,降低耦合度(如 Java Web 过滤器)。 命令模式(Command) 将请求封装为对象,支持请求的撤销(Undo)和恢复(Redo)。 备忘录模式(Memento) 保存对象的历史状态,以便以后恢复(如 撤销/恢复 操作)。 状态模式(State) 允许对象在不同状态下改变行为,避免大量 if-else 语句。 中介者模式(Mediator) 通过一个中介对象来协调多个对象之间的交互,避免对象间的直接通信。 迭代器模式(Iterator) 提供一种访问集合对象元素的方法,而不暴露集合的内部表示。 访问者模式(Visitor) 允许在不修改对象结构的情况下,向对象结构中添加新的行为(如 XML 解析)。 解释器模式(Interpreter) 用于定义语言的语法规则,并解释相应的表达式(如 SQL 解析)。
- 创建型:用于对象的创建,提高代码的灵活性和复用性
单例模式
- 饿汉式:类加载时机就已经把单例对象实例化出来了,所以他不存在线程安全问题。但是它会浪费内存,在还没使用的时候,就已经创建了实例。
- 懒汉式(线程不安全)
- 创建对象的时机修改为了在getInstance内部,需要时再创建,可以节约系统资源。
- getInstance方法在多个线程并发调用时,有可能会出现创建了多个实例,所以这是线程不安全的单例模式。
- 懒汉式(线程安全)
- 方法上加锁,这种加锁能保证线程安全问题,但是加锁的粒度较大,每次在调用getInstance方法的时候,都需要加锁,很显然,锁的开销很大。
- 懒汉式(线程安全-dcl模式)
- 双重判断,成员属性instance上,增加了volatile关键字,保障多线程对instance值的可见性以及禁止指令重排。
- 第一层空判断是为了锁的开销,只有为空的时候才加锁。第二层空判断是为了防止创建了多个实例。
- 静态内部类中加静态变量
- 静态内部类可以达到双检锁的效果。将instance放在了内部类中,不会在类加载的时候就加载实例,这个和饿汉式在类加载的时候就加载有区别。他只会在getInstance的时候,才会去加载内部类,此时才会去加载单例实例。并且instance是内部类类加载的时候才进行加载,所以线程安全问题也保证了。
- 枚举单例
- 这种不仅能避免多线程同步问题,还自动支持序列化机制,防止反序列化重新创建新的对象,防止多次实例化。
原型模式
- Java原型模式(Prototype Pattern)是一种创建型设计模式,其目的是通过复制现有对象来创建新的对象。
- 使用场景:
- 当对象创建的过程比较耗时或者比较复杂,例如需要进行复杂的计算或者涉及到网络请求等操作,可以使用原型模式来避免重复的初始化过程。
- 当需要创建的对象需要和其他对象进行协同工作时,例如需要创建一个包含多个对象的组合对象,可以使用原型模式来复制一个已有的组合对象,然后进行修改来创建新的组合对象。
- 当需要动态地增加或者删除一些对象时,可以使用原型模式来复制一个已有的对象,然后进行修改来创建新的对象。
- 当需要保护对象的复杂状态时,例如当一个对象的创建需要大量的数据初始化时,可以使用原型模式来保护这些数据,避免因为对象的复制而产生意外的副作用。
- 代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// 定义一个原型接口 interface Prototype { public Prototype clone(); } // 具体的原型类 class ConcretePrototype implements Prototype { public Prototype clone() { return new ConcretePrototype(); } } // 客户端代码 class Client { public static void main(String[] args) { Prototype prototype = new ConcretePrototype(); Prototype clone = prototype.clone(); } }
- 使用小结:
- Java中的Object类实现了Cloneable接口,这就意味着Java中的任何对象都可以实现原型模式。通过实现Cloneable接口,并重写Object类中的clone()方法,可以实现原型模式。例如 ArrayList、HashMap 等集合类都实现了Cloneable 接口,可以通过复制现有对象来创建新的对象。
- Java中的线程池也是使用了原型模式,线程池中的每个线程都是从原型线程中复制而来,而不是每次创建新的线程。
工厂模式
-
分为简单工厂模式、工厂方法模式、抽象工厂模式。定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的创建与使用相分离。创建型模式可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成。
-
简单工厂模式
- 简单工厂类会很庞大,负责创建所有产品的创建,如果要新增产品类,会修改工厂类,违背了开闭原则。并且违背了高聚合原则。
-
工厂方法模式
- 在简单工厂类基础上将工厂类也进行抽象化,每个工厂类只做一件事,那就是生产对应的对象,保证了单一职责原则,并且保证了开闭原则,如果要生产不同的对象,只需要提供对应的工厂实现类就可以。
- 在简单工厂类基础上将工厂类也进行抽象化,每个工厂类只做一件事,那就是生产对应的对象,保证了单一职责原则,并且保证了开闭原则,如果要生产不同的对象,只需要提供对应的工厂实现类就可以。
-
抽象工厂模式
- 和上面的工厂方法模式差不多,也是有对应的工厂实现类,区别是工厂类能生产不同级别的产品。
- 和上面的工厂方法模式差不多,也是有对应的工厂实现类,区别是工厂类能生产不同级别的产品。
建造者模式
- 将一个复杂对象的构建与它的表示分离,使同样的构建过程可以创建不同的表示。将一个复杂对象进行一步步的构建而成,扩展性好,可以灵活配置的属性,来生成不同的对象。
- 构建指的是对象的创建过程,通过一步步组装的形式最终创建对象,而客户端在此过程中只需要提供组装的属性,而无需关心组装的过程
- 表示指的是对象创建完后的形态或结构,创建完后,对象就形成不可变的结构。此时客户端可以放心使用该对象。
静态代理模式
- 由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用。
- 代理对象可以扩展目标对象的功能
- 其中静态代理要求代理类和被代理类实现同样的接口,旨在想扩展被代理类的某些功能,比如想添加些日志等行为
动态代理模式
动态代理模式和静态代理模式的区别是,静态代理在编译期就已经确定了代理类和被代理类的关系,而动态代理是在运行时通过Proxy创建了代理类,在代理类中通过反射动态调用了InvokeHandler接口的实现类的invoke方法,最后在invoke中统一调用被代理类的方法。
总结: 代理模式主要是通过代理类来控制对象的访问,主要涉及到访问权限、延迟加载、日志记录。比如有一个用户角色权限比较低,不能访问数据库,此时在调用对象的时候,判断权限而抛异常。再者比如在代理类中,可以延迟初始化被代理类。再比如可以通过日志记录被代理类的访问。
适配器模式
- 将一个类的接口转换成客户希望的另外一个接口,使得原本是由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式。类结构型模式的耦合度比后者高。
- 优点:客户端通过适配器可以透明地调用目标接口,复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
- 角色:目标类,它是真正被调用的类;适配者类,它是被访问和适配的现存组件库中的组件接口;适配器类:它是一个转换器,把适配者接口转换成目标接口,让客户能通过适配者调用目标接口。
- 类适配器模式:
在上面Adaptee是业务要被适配的类,适配器类ClassAdapter通过继承自Adaptee和被适配器类耦合,不方便扩展,最好的方案是将被适配器类交给调用方自己传进来。
- 对象适配器模式:
- 从这里可以看出,对象适配模式更加的符合开闭原则,并且更加容易扩展。
桥接模式
- 强调的是抽象与实现分离,使它们可以独立变化。使用组合关系代替继承关系来实现。降低了抽象和实现两个可变维度的耦合度。和上面适配器模式不同,这里强调的是多种抽象的组合。它是有以下角色:
- 抽象化角色:定义抽象类,并包含一个对实现化对象的引用。
- 扩展抽象化角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
- 实现化角色:定义实现化角色的接口,供扩展抽象化角色调用。
- 具体实现化角色:给出实现化角色接口的具体实现。
- 实现化角色在这里是依附于抽象化角色上的,也就是最终是被扩展化角色所使用。
- 上面的color是一个实现化角色,其中具体实现化角色分为红色和黄色。抽象化角色是Bag类,它的扩展抽象化角色有Wallet和HandBag。他们使用了color这个实现化角色。
装饰者模式
- 在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。
- 有以下角色:
- 抽象构件角色:定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件角色:实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
- 上面ConcreteDecorator是具体的的装饰角色,它是持有了具体的构件角色ConcreteComponent,在调用具体的构建角色方法外,还调用了自己的另外一个方法,这个是和桥接模式的一个区别之处。并且装饰者模式的抽象的装饰角色实现了抽象构件角色,但是桥接模式中,抽象化构件角色中是不实现实现化角色接口的。
外观模式
- 是迪米特法则的体现,比如一个系统想调用子系统的某个方法的时候,为了降低客户端调用系统的时候,将调用子系统的方法,通过统一的中心来调用子系统。分为以下角色:
享元模式
- 相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
- 具体有如下角色:
- 抽象享元角色:是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
- 具体享元角色:实现抽象享元角色中所规定的接口。
- 非享元角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
- 享元工厂角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
上面享元工厂通过key来管理不同的享元角色,而享元角色持有了非享元角色的接口引用。上面例子中前3次获取的是同一个享元角色,后两次获取的是同一个享元角色。