七大设计原则
原则 | 主要内容 | 解决什么样的问题 | 如何解决问题 |
---|---|---|---|
单一职责 | 每个类、接口或方法有单独的职责 | 职责模糊不清,承担多种功能,修改的可能性会变大,耦合更高 | 将一个类拆分为多个类,接口,方法,具体根据复杂程度决定 |
接口隔离 | 只实现需要的接口 | 实现的接口种包含不必要的方法 | 将接口拆分为多个,只实现想实现的 |
依赖倒置 | 依赖接口,而不是具体实现 | 依赖关系中,依赖的是具体实现类,而不是接口,耦合度增大 | 抽取接口,依赖接口 |
里氏替换 | A继承B,且将所有B类的引用换成A类不改变逻辑 | 修改父类会影响到其他子类 | 继承关系中,子类尽量只做扩展,不做重写 |
开闭原则 | 对扩展开放,对修改关闭 | 修改会引起其依赖的逻辑改动,为了避免大批量的修改逻辑 | 修改代码结构,增加接口或抽象类,扩展接口实现类 |
迪米特法则 | 最少知道原则,尽量只对外提供public的方法 | ||
组合复用 | 尽量使用依赖,组合,聚合等代替继承关系 | 继承关系耦合度更高 | 使用依赖,组合聚合等代替 |
六种关系
关系 (由低到高) |
代码实现 | 实现形式 | uml |
---|---|---|---|
依赖 | class B { public A a(A a){} } |
类A中使用了类B,B作为方法参数,返回值,局部变量,静态方法调用等 | 虚线加箭头 |
关联 | class B { A a; } |
成员变量 | 实线加箭头 |
聚合 | class B { A a; public B(A a) { this.a = a; } } |
整体和个体(零件),整体由个体组成,整体不存在,个体可以依然存在。 代码实现形式通常是构造函数中引入个体类作为参数,整体对象生命周期结束后,个体可以继续存在。 |
实线加空心菱形 |
组合 | class B { A a = new A(); / public B() { this.a = new A();}/ } |
整体和(组成)部分,整体由部分组成,整体不存在,部分也不存在了。 代码实现形式是直接作为成员变量赋值,不是以参数的形式传入。整体生命周期结束,部分也结束,(级联删除)关联性更强。 |
实现加实心菱形 |
实现 | class B implements A{} | 实现抽象类 | 虚线加三角形箭头 |
继承(泛化) | class B extends B{} | 继承非抽象类 | 实线加三角形箭头 |
uml中的虚线与实线的区别 虚线代表:依赖 实线代表:关联 实线通常会带有多重性的表达,一对多,一对一等。
设计模式
某类问题的通用解决方案,不是代码。
设计模式是为了提高代码的维护性,可扩展性和通用性,降低软件的复杂度而存在的。
模式 | 功能 | 适用场景 | 举例 | 补充 |
---|---|---|---|---|
单例 (创建型) |
保证整个程序中只能产生一个实例。 | 1. 频繁进行创建和销毁的对象。 1. 创建对象耗时过多或消耗资源过多 1. 经常用到的对象 1. 工具类对象 1. 频繁访问数据库或文件的对象(SessionFactory,数据源) |
hibernate的SessionFactory。 jdk的Runtime方法(饿汉式)。 |
推荐使用枚举,双重检查。 |
工厂 (创建型) |
不使用new创建对象,而是使用工厂方法创建。将具体的实现放到子类实现,对外提供抽象工厂类。 优点:调用者只需要知道要创建对象名和抽象类;扩展只需要加具体实现类; 缺点:增加新产品时,需要增加相应的接口和实现类。 |
需要更具不同的条件创建不同的对象时。 抽象出工厂类,通过多个实现类做不同的创建实现。 |
1. 提供了多个数据库配置,创建数据库连接时根据不同的数据库类型创建不同的连接对象。 1. JDK的Calendar(简单工厂),创建对象时,有参情况下根据不同的后缀确定不同的时区对象. |
分为简单工厂和工厂模式。 |
抽象工厂 | 围绕一个超级工厂创建其他的工厂。 可以理解成是在工厂模式的基础上在抽象出一层来。 |
适用于:产品存在多个种类,只消费其中某一类的产品。 |
分类
- 创建型
单例,抽象工厂,原型,建造者,工厂模式
- 结构型
适配器,桥接,装饰,组合,外观,享元,代理模式
- 行为型
模板方法,命令,访问者,迭代器,观察者,中介者,备忘录,解释器,状态,策略,责任链
创建型
单例
保证整个程序中只能产生一个实例。比如hibernate的SessionFactor。充当数据存储源的代理,负责创建session,只需要存在一个就够了。
适用场景:
- 频繁进行创建和销毁的对象。
- 创建对象耗时过多或消耗资源过多
- 经常用到的对象
- 工具类对象
- 频繁访问数据库或文件的对象(SessionFactory,数据源)
举例:
- hibernate的SessionFactory。
-
饿汉式(静态常量)
class singleton {
private final static singleton instance = new singleton();
private singleton() {}
public static singleton getInstance() {
return instance;
}
}
- 私有的无参构造函数
- 私有的静态的final成员变量
- 成员变量直接初始化
- 公共的静态的获取实例方法。
为什么要使用私有的无参构造函数? 需要防止通过构造函数创建多个实例。私有的无参构造函数是必须的。 为什么成员变量是静态的final? 静态是因为外部无法通过new创建对象,所以通过static修饰,外部通过类直接获取 final是因为单例对象需要保证只有一个,就要求对象无法被修改。final修饰的对象无法被修改。 为什么要使用静态的获取方法? 因为构造函数是私有的,无法直接创建对象; 但是又必须创建对象。所以返回对象是静态,就要求方法必须是静态的。 final是不是必须要加?
基于类加载机制,在类装载时进行了实例化。
优点:简单,线程安全
缺点:无法做到懒加载,有可能资源浪费;无法防止反射修改;不能确定有其他方式触发类加载。
饿汉式(静态代码块)
class Singleton {
private final static Singleton instance;
static {
instance = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
将静态常量直接new,改成了在静态代码块中赋值。其他一样。
懒汉式(线程不安全)
class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null ) {
instance = new Singleton();
}
return instance;
}
}
多线程不安全,在一个线程进行为空判断后,准备创建对象还未创建时,另一个线程同样为空判断为true,这样就会创建多个实例。 实际开发中不会使用。
懒汉式(线程安全)
class Singleton {
private static Singleton instance;
private Singleton() {}
public synchronized static Singleton getInstance() {
if (instance == null ) {
instance = new Singleton();
}
return instance;
}
}
线程安全,但是效率不高
双重检查锁
class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null ) {
instance = new Singleton();
}
}
}
return instance;
}
}
volatile:为了能够在变量被修改是能够立即更新到主存(可见性,防止指令重排序)。从null变为非空时。 synchronized:为了能够保持线程同步。只允许一个线程执行。 两次非空判断:
- 为了不让同步的代码反复的执行。不为null时直接返回实例,为空时需要第一次创建对象,创建对象时给singleton加锁,然后再次判断是否为空。
- 为的是检测是否有多线程在第一次判断过程中已经创建了对象。所以需要二次判断。
静态内部类
class Singleton {
private Singleton() {}
private static class InnerClass {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return InnerClass.instance;
}
}
外部类装载时,静态内部类不会被装载 调用getInstance方法时,静态内部类加载。 静态内部类如何保证线程安全? 1. 静态内部类如何实线懒加载? 静态内部类在外部类加载时不会立即被加载,只有在使用到内部类的时候才会加载。
优点:能够保证线程安全,也能实现懒加载。
枚举
enum Singleton {
INSTANCE;
}
枚举的实现 枚举获取的相同实例就是相同的对象,枚举只拥有一个实例时,就保证了单例。
优点:简单,高效,线程安全,能够防止反序列化重新创建对象。推荐使用。
工厂模式
核心本质是实例化对象不使用new,而是用工厂方法实现。
适用场景:
面对接口的多种实现类(多态),根据不同的条件获取不同对象的方法。改变原来的直接new实现类的方法,将实例化对象的代码提取出来,通过一个抽象接口工厂来统一管理创建不同的对象。达到解耦目的
举例:
- jdk的Calendar(简单工厂),创建对象时,有参情况下根据不同的后缀确定不同的时区对象。
- 通过工厂模式 + 配置文件的形式能够解除耦合,通过静态代码读取配置文件只执行一次,将键值对进行保存,根据传入的键获取相应的值的对象。
工厂模式三种模式:
- 简单工厂模式
根据传入参数类型的判断,来创建不同的对象,代码耦合度较高。
如果需要增加新的产品(对象),需要覆盖原有的代码。
结构简单,但是不符合开闭原则。
//举例,如果需要新增新的类型,则需要修改工厂类的创建方法。
class Factory {
public product create(int type) {
if(type == 1) {
return new A();
} else if(type == 2) {
return new B();
}
}
}
- 工厂方法模式
相比简单工厂来说,不需要对原有的代码改动,只进行扩展。
扩展新种类时,只需要实现Base接口,创建新种类的工厂方法,实现工厂接口
优点:符合设计原则,面向对接口,面向对象。
缺点:需要有很多实现类
interface Base{
public void getName();
}
class A implements Base{
public void getName() {
System.out.println("A");
}
}
class B implements Base{
public void getName() {
System.out.println("B");
}
}
interface Factory{
public Base create();
}
class AFactory implements Factory{
public Base create() {
return new A();
}
}
class BFactory implements Factory{
public Base create() {
return new B();
}
}
class Consume{
public static void main(String[] args) {
Base a = new AFactory().create();
Base b = new BFactory().create();
}
}
抽象工厂模式
抽象工厂是工厂的工厂,提供一个创建一系列相关或相互依赖对象的接口。
原型模式
创建新对象比较复杂时,使用原型模式来简化创建过程。
存在一个对象,可以通过原型模式得到多个相同属性的对象(但不是同一个对象)
类实现cloneable接口,重写clone方法。通过对象.clone()的方法获取相同属性对象。
原对象的基本类型属性值传递
原对象的引用类型属性引用传递。
缺点:
深拷贝与浅拷贝
建造者模式
一种对象构建模式,将复杂对象的建造过程抽象出来,使得这个过程的不同实现方法可以构造出不同的对象。
一步一步创建一个复杂对象,用户只需要知道对象的类型和内容,无需了解具体实现细节。
建造者模式的四个角色