记录设计模式相关知识,包括设计模式定义,设计原则(单一职责,开闭原则,依赖倒置,里式替换,接口隔离,迪米特原则,组合聚合复用原则),单例模式,原型模式并提供代码示例
文章目录
- 一、设计模式介绍
- 二、设计原则
- 三、设计模式
- 单例模式
- 原型模式
正文内容:
一、设计模式介绍
设计模式是一种设计规范,对于一类问题合理解决的思路与方式,更高效解决一类问题,对于编码来讲就是可以提高程序代码的可读性,可扩展性,复用性
- 可以提高程序员的思维能力,编程能力和设计能力
- 使程序更加标准化,使软件开发效率大大提高
- 使设计的代码可重用性高,可读性强,可靠性高,灵活性好,可维护性强
- 更好的理解源码架构
通过设计模式帮助程序员构建更完善的系统编码;编码过程中找到需求会发生变化的地方与不会发生变化的地方,将两者分离开;面向接口编程,而不是面向实现编程;实现高内聚低耦合
二、设计原则
- 单一职责
在设计的时候尽量让类,或者方法只处理单一事件(高内聚低耦合) - 开闭原则
对扩展开放,对修改关闭;对于类或者方法进行功能增强是支持的;对于类或者方法的修改关闭,不在原有类或者方法上面进行修改;为满足开闭原则,一般使用抽象化设计 - 里氏替换(LSP)
一个超类的对象应该能够被一个子类的对象替换,而不影响程序的正确性
换句话说,如果一个程序使用了基类,那么它应该能够使用任何派生类的对象,只要该派生类满足基类的行为期望。LSP确保子类可以替换其基类
代码示例如下:
interface Shape {int area();
}class Rectangle implements Shape {private int width;private int height;public Rectangle(int width, int height) {this.width = width;this.height = height;}@Overridepublic int area() {return width * height;}
}class Square extends Rectangle {public Square(int side) {super(side, side);}
}class AreaCalculator {public static int computeArea(Shape[] shapes) {int totalArea = 0;for (Shape shape : shapes) {totalArea += shape.area();}return totalArea;}
}public class Main {public static void main(String[] args) {Shape[] shapes = new Shape[] {new Rectangle(2, 3),new Square(2),new Rectangle(3, 4),new Square(3)};System.out.println(AreaCalculator.computeArea(shapes));}
}
在这个例子中,我们有一个Shape接口,其中有一个抽象方法area,它将被子类重写。Rectangle类实现了area方法,Square类继承了Rectangle类并重写了Square构造函数,以保持宽度和高度相等,创建了一个正方形。
AreaCalculator类的computeArea方法接受一个Shape对象数组,并计算所有形状的总面积。此方法可以与任何类型的Shape对象(包括Rectangle和Square)一起使用,而不必知道特定类型。这是因为Square类满足Shape类的预期行为,可以代替Rectangle,而不影响程序的正确性。
LSP是一个重要的原则,因为它通过继承实现代码复用,从而帮助创建灵活和易于维护的代码库。它还有助于防止错误,并确保即使在代码发生更改时程序仍然可以正常工作。
- 依赖倒置
上层模块不应该依赖底层模块,都应该依赖抽象;抽象不依赖于细节,细节应该依赖于抽象 - 接口隔离
使用多个接口,而不是使用单一的总接口,不强迫新功能实现不需要的功能 - 迪米特原则
一个对象对于其他对象有最少了解
比如说存在学院类,学校类,管理类,管理类存在方法,需要将学院信息,学校信息进行输出;此时根据迪米特原则,分别将学员信息,学校信息访问方法设计在学院类与学校类,在管理类中调取这两个访问方法即可 - 组合/聚合复用原则
模块A需要复用模块B时,根据模块之间的关联程度,尽量使用组合/聚合关系进行复用,减小模块之间的耦合程度
三、设计模式
单例模式
单例模式
:类是一类事物共同属性,行为的抽象;具体到类中事物共同属性将会抽象成为类中的成员属性,行为将会抽象成为类中的方法,类中还包括构造器,用于创建出一个具体的事物,也就是Java中的对象,通过构造器,可以创建多个对象,但是有的时候程序不需要多个对象,对于这个类只需要创建一个对象时,就需要使用单例模式
实现思路
:将构造器私有化,外部无法创建对象;但是此时需要该类对象,所以需要提供外部方法供其他类访问该对象;通过将类引用使用static修饰保证对象在整个程序运行期间的存在;程序还需要保证对象的唯一性,最简单(不安全,不建议)的实现可以这样子:
public class Singleton{private static Singleton singleton = new Singleton();private Singlrton(){}public static Singleton getInstance(){return singleton;}
}
上面代码在对象引用位置就创建对象;就和饿汉看见食物之后很急切的开始吃食一样,所以上述代码有很形象的名称:饿汉式单例模式,与之对应的还包括懒汉模式;懒汉模式与饿汉模式最大的差别是两者创建对象的时期不一样,懒汉式只有在对象第一次使用的时候创建,将对象的创建时期推后,需要考虑线程安全问题,线程安全的懒汉式单例模式代码如下所示:
public class Singleton {private static volatile Singleton singleton;private Singleton() {}public static Singleton getInstance() {if (singleton==null){synchronized (Singleton.class){if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}
在双重检锁单例模式中判断instance是否为null的原因是,如果instance已经被实例化了,那么就没有必要进入加锁的代码块,直接返回已经实例化的instance即可。而第二次判断instance是否为null的原因是,在多线程环境下,可能存在多个线程同时通过了第一次判断,进入了加锁的代码块,但只有一个线程会获得锁,实例化instance对象并将其赋值给instance变量。如果没有第二次判断,那么其他线程在获得锁后也会再次实例化一个instance对象,这就破坏了单例模式的原则,因为这样就不再只有一个instance对象了。
在第二次判断中,为了确保在多线程环境下,instance变量的可见性和一致性,通常会使用volatile关键字进行修饰。volatile关键字可以保证所有线程对instance变量的访问都是直接读写主存,而不是读写线程私有的副本,从而避免了可能的可见性和一致性问题。
原型模式
原型模式
:是一种创建型设计模式,它允许通过克隆现有对象来创建新对象,而不是通过实例化新的对象并赋值来创建新对象。原型模式主要涉及到两个角色,即原型(Prototype)和具体原型(Concrete Prototype)。其中,原型是一个抽象类或接口,定义了一个 clone() 方法,用于复制自身;具体原型则是实现了原型接口的具体类,它的 clone() 方法可以创建一个当前对象的副本。通过复制现有的对象,原型模式可以避免在创建对象时昂贵的构造过程,从而提高了创建对象的效率。
在使用原型模式时,通常需要在程序中创建一个原型对象,并将其存储在一个原型管理器(Prototype Manager)中。当需要创 建新对象时,可以从原型管理器中获取一个原型对象,然后通过克隆方法创建新对象。
以下是原型模式的基本结构:
-
抽象原型(Prototype):定义一个克隆自身的接口方法。
-
具体原型(Concrete Prototype):实现抽象原型的克隆方法,用于复制自身。
-
原型管理器(Prototype Manager):用于存储和管理原型对象。
原型模式的优点包括:
-
可以避免创建对象时昂贵的构造过程,从而提高创建对象的效率。
-
可以动态添加和删除原型对象。
-
可以实现深拷贝和浅拷贝,以便灵活地复制对象。
原型模式的缺点包括:
-
需要为每个类配备一个克隆方法,这增加了代码量。
-
克隆方法对于含有循环引用或引用其他不可序列化的对象的复制会比较困难。
-
使用原型模式时需要注意深拷贝和浅拷贝的问题。
代码实现如下所示:
public abstract class Shape implements Cloneable {private String id;protected String type;public String getId() {return id;}public void setId(String id) {this.id = id;}public String getType() {return type;}public abstract void draw();@Overridepublic Object clone() {Object clone = null;try {clone = super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return clone;}
}public class Rectangle extends Shape {public Rectangle() {type = "Rectangle";}@Overridepublic void draw() {System.out.println("Inside Rectangle::draw() method.");}
}public class Square extends Shape {public Square() {type = "Square";}@Overridepublic void draw() {System.out.println("Inside Square::draw() method.");}
}public class Circle extends Shape {public Circle() {type = "Circle";}@Overridepublic void draw() {System.out.println("Inside Circle::draw() method.");}
}public class ShapeCache {private static Map<String, Shape> shapeMap = new HashMap<>();public static Shape getShape(String shapeId) {Shape cachedShape = shapeMap.get(shapeId);return (Shape) cachedShape.clone();}public static void loadCache() {Circle circle = new Circle();circle.setId("1");shapeMap.put(circle.getId(), circle);Square square = new Square();square.setId("2");shapeMap.put(square.getId(), square);Rectangle rectangle = new Rectangle();rectangle.setId("3");shapeMap.put(rectangle.getId(), rectangle);}
}public class PrototypePatternDemo {public static void main(String[] args) {ShapeCache.loadCache();Shape clonedShape = (Shape) ShapeCache.getShape("1");System.out.println("Shape : " + clonedShape.getType());Shape clonedShape2 = (Shape) ShapeCache.getShape("2");System.out.println("Shape : " + clonedShape2.getType());Shape clonedShape3 = (Shape) ShapeCache.getShape("3");System.out.println("Shape : " + clonedShape3.getType());}
}
这里定义了一个抽象类Shape,包含一个draw抽象方法和一个clone方法,并有三个子类分别继承自Shape,实现了draw方法。ShapeCache类用于缓存Shape对象,其中使用getShape方法获取缓存的对象并使用clone方法进行复制。在PrototypePatternDemo类中,通过缓存获取Shape对象并复制,从而达到原型模式的效果。