14.设计模式
- 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的代码设计经验的总结。
14.1.设计模式六大原则
- 单—职责原则(Single Responsibility Principle):不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
- 里氏替换原则(Liskov Substitution Principle):
- 定义1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。
- 定义2:所有引用父类的地方必须能透明地使用其子类的对象。即任何父类可以出现的地方,子类一定可以出现
- 不建议子类重写父类的方法;如果要重写,父类定义为抽象方法
- 依赖倒置原则(Dependence lnversion Principle):高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
- 接口隔离原则(Interface Segregation Principle):客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
- 迪米特法则(Law Of Demeter):一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立
- 开闭原则(Open Closed Principle):在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果
- 对新增代码开放,对修改代码封闭。
- 开闭原则无非就是想表达这样一层意思:用抽象构建框架,用实现扩展细节
总结:
- 单一职责原则告诉我们实现类要职责单一;
- 里氏替换原则告诉我们不要破坏继承体系;
- 依赖倒置原则告诉我们要面向接口编程;
- 接口隔离原则告诉我们在设计接口的时候要精简单一;
- 迪米特法则告诉我们要降低耦合;
- 开闭原则是总纲,告诉我们要对扩展开放,对修改关闭。
14.2.单例模式
14.2.1.介绍
- 单例模式(Singleton Pattern):单例设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
- 注意:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例
- 目的:
- 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- 主要解决:一个全局使用的类频繁地创建与销毁。
- 何时使用:想控制实例数目,节省系统资源的时候。
- 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
- 关键代码:构造函数是私有的。
- 实现方式
- 构造方法私有
- 提供静态方法给外部类来获得实例
- 包含本身类型的成员变量
- 应用实例:
- 一个班级只有一个班主任。
- Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
- 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
- 优缺点:
- 优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 避免对资源的多重占用(比如写文件操作)。
- 缺点:
- 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
- 优点:
- 使用场景:
- 要求生产唯一序列号。
- WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注:有时getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
14.2.2.懒汉式线程不安全
- 对于多个线程调用,无法保证同一个实例,同时,只有真正调用getInstance方法才会完成实例化,否则该对象一直是null
public class Singleton{ private static Singleton singleton; private Singleton(){}; public static Singleton getInstance(){ if(singleton == null) singleton = new Singleton(); return singleton } }
14.2.3.懒汉式线程安全
- 相比于线程不安全的懒加载,只是在方法中添加了synchronized同步关键字,将其变成同步方法
public class Singleton{ private static Singleton singleton; private Singleton(){}; public synchronized static Singleton getInstance(){ if(singleton == null) singleton = new Singleton(); return singleton } }
- 静态内部类实现,无需加synchronize关键字:
public class Singleton{ private Singleton(){}; public synchronized static Singleton getInstance(){ return singletonInner.getSingleton(); } static class SingletonInner{ private static Singleton singleton; static{ singleton = new Singleton(); //懒汉模式 } public static Singleton getSingleton(){ return singleton; } } }
14.2.4.饿汉式
- 当该类加载的时候,静态变量就会完成初始化
- 类加载的方式是按需加载,且只加载一次,因此,在单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用。即在线程访问单例对象之前就已经创建好了。再加上,由于一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例。线程每次都只能也必定只可以拿到这个唯一的对象。即饿汉式单例天生就是线程安全的。
public class Singleton{ private static Singleton singleton = new Singleton(); //或采静态代码块形式,不在声明的时候实例化也可以 /* static{ singleton = new Singleton(); } */ private Singleton(){}; public static Singleton getInstance(){ if(singleton == null) singleton = new Singleton(); return singleton } }
14.3.工厂模式
14.3.1.介绍
- 工厂模式(Factory Pattern):工厂设计模式属于创建型模式,它提供了一种创建对象的最佳方式。工厂模式中,在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
- 意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
- 主要解决:主要解决接口选择的问题。
- 何时使用:我们明确地计划不同条件下创建不同实例时。
- 如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。
- 关键代码:创建过程在其子类执行。
- 应用实例:
- 需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。
- Hibernate 换数据库只需换方言和驱动就可以。
- 优缺点:
- 优点:
- 一个调用者想创建一个对象,只要知道其名称就可以了。
- 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
- 缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
- 优点:
- 使用场景:
- 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
- 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
- 设计一个连接服务器的框架,需要三个协议,“POP3”、“IMAP”、“HTTP”,可以把这三个作为产品类,共同实现一个接口。
- 注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
14.3.2.使用工厂模式
- 创建一个 Shape 接口和实现 Shape 接口的实体类。定义工厂类 ShapeFactory。
- FactoryPatternDemo 类使用 ShapeFactory 来获取 Shape 对象。它将向 ShapeFactory 传递信息,以便获取它所需对象的类型。
//1.创建一个接口 public interface Shape { void draw(); } //2.创建接口的实体类 public class Rectangle implements Shape { @Override public void draw() { System.out.println("Inside Rectangle::draw() method."); } } public class Square implements Shape { @Override public void draw() { System.out.println("Inside Square::draw() method."); } } public class Circle implements Shape { @Override public void draw() { System.out.println("Inside Circle::draw() method."); } } //3.创建一个工厂,用于生成基于给定信息的实体类的对象。 public class ShapeFactory { //使用 getShape 方法获取形状类型的对象 public Shape getShape(String shapeType){ if(shapeType == null){ return null; } if(shapeType.equalsIgnoreCase("CIRCLE")){ return new Circle(); } else if(shapeType.equalsIgnoreCase("RECTANGLE")){ return new Rectangle(); } else if(shapeType.equalsIgnoreCase("SQUARE")){ return new Square(); } return null; } } //4.使用该工厂,通过传递类型信息来获取实体类的对象。 public class FactoryPatternDemo { public static void main(String[] args) { ShapeFactory shapeFactory = new ShapeFactory(); //获取 Circle 的对象,并调用它的 draw 方法 Shape shape1 = shapeFactory.getShape("CIRCLE"); //调用 Circle 的 draw 方法 shape1.draw(); //获取 Rectangle 的对象,并调用它的 draw 方法 Shape shape2 = shapeFactory.getShape("RECTANGLE"); //调用 Rectangle 的 draw 方法 shape2.draw(); //获取 Square 的对象,并调用它的 draw 方法 Shape shape3 = shapeFactory.getShape("SQUARE"); //调用 Square 的 draw 方法 shape3.draw(); } } /* 输出结果 Inside Circle::draw() method. Inside Rectangle::draw() method. Inside Square::draw() method. */
14.4.代理模式
14.4.1.介绍
- 代理模式(Proxy Pattern):一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。在代理模式中,创建具有现有对象的对象以便向外界提供功能接口。
- 意图:为其他对象提供一种代理以控制对这个对象的访问。
- 主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
- 何时使用:想在访问一个类时做一些控制。
- 如何解决:增加中间层。
- 关键代码:实现与被代理类组合。
- 应用实例:
- Windows 的快捷方式。
- 买火车票不一定在火车站买,也可以去代售点。
- 一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。
- spring aop。
- 优缺点:
- 优点:
- 职责清晰。
- 高扩展性。
- 智能化。
- 缺点:
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
- 优点:
- 使用场景:按职责来划分,通常有以下使用场景:
- 远程代理。
- 虚拟代理。
- Copy-on-Write 代理。
- 保护(Protect or Access)代理。
- Cache代理。
- 防火墙(Firewall)代理。
- 同步化(Synchronization)代理。
- 智能引用(Smart Reference)代理。
- 注意事项:
- 和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
- 和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
14.4.2.静态代理模式
- 创建一个 Image 接口和实现了 Image 接口的实体类。ProxyImage 是一个代理类,减少 RealImage 对象加载的内存占用。ProxyPatternDemo 类使用 ProxyImage 来获取要加载的 Image 对象,并按照需求进行显示。
//1.创建一个接口 public interface Image { void display(); } //2.创建实现接口的实体类,委托类 public class RealImage implements Image { private String fileName; public RealImage(String fileName){ this.fileName = fileName; loadFromDisk(fileName); } @Override public void display() { System.out.println("Displaying " + fileName); } private void loadFromDisk(String fileName){ System.out.println("Loading " + fileName); } } public class ProxyImage implements Image{ private RealImage realImage; private String fileName; public ProxyImage(String fileName){ this.fileName = fileName; } @Override public void display() { if(realImage == null){ realImage = new RealImage(fileName); } realImage.display(); } } //3.当被请求时,使用 ProxyImage 来获取 RealImage 类的对象 public class ProxyPatternDemo { public static void main(String[] args) { Image image = new ProxyImage("test_10mb.jpg"); // 图像将从磁盘加载 image.display(); System.out.println(); // 图像不需要从磁盘加载 image.display(); } } /* 输出结果 Loading test_10mb.jpg Displaying test_10mb.jpg Displaying test_10mb.jpg */
14.4.3.JDK动态代理
- Image接口和RealImage实现类不变
//1.创建一个接口 public interface Image { void display(); } //2.创建实现接口的实体类,委托类 public class RealImage implements Image { private String fileName; public RealImage(String fileName){ this.fileName = fileName; loadFromDisk(fileName); } @Override public void display() { System.out.println("Displaying " + fileName); } private void loadFromDisk(String fileName){ System.out.println("Loading " + fileName); } } //3.使用JDK动态代理 public class JDKDynamicProxy { public static void main(String[] args) { //在main方法中保存生成的字节码文件,保存到working directory //System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); Image image = new RealImage("test_10mb.jpg"); //生成一个JDK动态代理类的代理对象,和RealImage类同样都实现了Image接口 //注意,这里仅仅是生成一个代理对象,仅仅是一个声明,所以应该是先执行display方法,然后才会回调invoke方法 Image jdkProxy = (Image) Proxy.newProxyInstance( //getInterfaces可以获得这个类所实现的所有接口 RealImage.class.getClassLoader(),RealImage.class.getInterfaces(), new InvocationHandler(){ /** * @param proxy 代理对象 * @param method 正在执行的方法 * @param args 正在执行的方法所携带的参数 * @return Object 委托类方法执行的结果 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //通过调试查看方法调用 //System.out.printlm(method.getName()); System.out.println("请稍等"); Object invoke = method.invoke(image, args); System.out.println("展示完毕"); return invoke; } }); // //lambda表达式写法,更为简洁 // Image imageProxy = (Image) Proxy.newProxyInstance(RealImage.class.getClassLoader(),RealImage.class.getInterfaces(), // //arg名称为了防止和main方法中的参数args重名,取任意均可 // (proxy,method,arg)-> { // System.out.println("请稍等"); // Object invoke = method.invoke(image, arg); // System.out.println("展示完毕"); // return invoke; // }); //在这里,代理对象是imageProxy,方法名是display,参数是null,其实是提供给invoke方法的 jdkProxy.display(); } }
14.4.4.Cglib动态代理
- Image接口和RealImage实现类不变
//1.创建一个接口 public interface Image { void display(); } //2.创建实现接口的实体类,委托类 public class RealImage implements Image { private String fileName; public RealImage(String fileName){ this.fileName = fileName; loadFromDisk(fileName); } @Override public void display() { System.out.println("Displaying " + fileName); } private void loadFromDisk(String fileName){ System.out.println("Loading " + fileName); } } //3.maven导入依赖 <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.12</version> </dependency> //4.使用Cglib动态代理 public class CglibDynamicProxy { public static void main(String[] args) { //设置编译后的代理对象的class文件保存路径 //System.setProperty(DebuggingClassWriter,DEBUG_LOCATION_PROPERTY,"D:\\temp"); final Image image = new RealImage("test_10mb.jpg"); //cglib动态代理方式继承了Image的实现类,即继承了RealImage类,与JDK动态代理方式不同 //因此在这里,通过RealImage或Image来接收均可 //class参数使用接口和实现类均可,InvocationHandler接口和JDK的不是同一个 Image cglibProxy = (Image) Enhancer.create(Image.class, new InvocationHandler() { public Object invoke(Object o, Method method, Object[] objects) throws Throwable { //通过调试查看方法调用 //System.out.printlm(method.getName()); System.out.println("请稍等"); Object invoke = method.invoke(image, objects); System.out.println("展示完毕"); return invoke; } }); // Image cglibProxy = (Image) Enhancer.create(Image.class,(InvocationHandler)(o,method,objects)->{ // System.out.println("请稍等"); // Object invoke = method.invoke(image, objects); // System.out.println("展示完毕"); // return invoke; // }); cglibProxy.display(); } }
目标对象实现了某个接口,可以使用JDK动态代理需要或Cglib动态代理方式均可,否则只能使用cglib方式。
14.5.建造者模式
14.5.1.介绍
- 建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。
- 意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
- 主要解决:主要解决在软件系统中,有时候面临着”一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
- 何时使用:一些基本部件不会变,而其组合经常变化的时候。
- 如何解决:将变与不变分离开。
- 关键代码:建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。
- 应用实例:
- 去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的”套餐”。
- JAVA 中的 StringBuilder的append方法
- 优缺点:
- 优点:
- 建造者独立,易扩展。
- 便于控制细节风险。
- 缺点:
- 产品必须有共同点,范围有限制。
- 如内部变化复杂,会有很多的建造类。
- 优点:
- 使用场景:
- 需要生成的对象具有复杂的内部结构。
- 需要生成的对象内部属性本身相互依赖。
- 注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
14.5.2.设计
- 假设一个快餐店的商业案例,其中,一个典型的套餐可以是一个汉堡(Burger)和一杯冷饮(Cold drink)。汉堡(Burger)可以是素食汉堡(Veg Burger)或鸡肉汉堡(Chicken Burger),它们是包在纸盒中。冷饮(Cold drink)可以是可口可乐(coke)或百事可乐(pepsi),它们是装在瓶子中。
- 我们将创建一个表示食物条目(比如汉堡和冷饮)的 Item 接口和实现 Item 接口的实体类,以及一个表示食物包装的 Packing 接口和实现 Packing 接口的实体类,汉堡是包在纸盒中,冷饮是装在瓶子中。
- 我们创建一个 Meal 类,带有 Item 的 ArrayList 和一个通过结合 Item 来创建不同类型的 Meal 对象的 MealBuilder。BuilderPatternDemo 类使用 MealBuilder 来创建一个 Meal。
//1.创建一个表示食物条目和食物包装的接口
public interface Item {
public String name();
public Packing packing();
public float price();
}
public interface Packing {
public String pack();
}
//2.创建实现 Packing 接口的实体类
public class Wrapper implements Packing {
@Override
public String pack() {
return "Wrapper";
}
}
public class Bottle implements Packing {
@Override
public String pack() {
return "Bottle";
}
}
//3.创建实现 Item 接口的抽象类,该类提供了默认的功能
public abstract class Burger implements Item {
@Override
public Packing packing() {
return new Wrapper();
}
@Override
public abstract float price();
}
public abstract class ColdDrink implements Item {
@Override
public Packing packing() {
return new Bottle();
}
@Override
public abstract float price();
}
//4.创建扩展了 Burger 和 ColdDrink 的实体类。
public class VegBurger extends Burger {
@Override
public float price() {
return 25.0f;
}
@Override
public String name() {
return "Veg Burger";
}
}
public class ChickenBurger extends Burger {
@Override
public float price() {
return 50.5f;
}
@Override
public String name() {
return "Chicken Burger";
}
}
public class Coke extends ColdDrink {
@Override
public float price() {
return 30.0f;
}
@Override
public String name() {
return "Coke";
}
}
public class Pepsi extends ColdDrink {
@Override
public float price() {
return 35.0f;
}
@Override
public String name() {
return "Pepsi";
}
}
//5.创建一个 Meal 类,带有上面定义的 Item 对象。
public class Meal {
private List<Item> items = new ArrayList<Item>();
public void addItem(Item item){
items.add(item);
}
public float getCost(){
float cost = 0.0f;
for (Item item : items) {
cost += item.price();
}
return cost;
}
public void showItems(){
for (Item item : items) {
System.out.print("Item : "+item.name());
System.out.print(", Packing : "+item.packing().pack());
System.out.println(", Price : "+item.price());
}
}
}
//6.创建一个 MealBuilder 类,实际的 builder 类负责创建 Meal 对象
public class MealBuilder {
public Meal prepareVegMeal (){
Meal meal = new Meal();
meal.addItem(new VegBurger());
meal.addItem(new Coke());
return meal;
}
public Meal prepareNonVegMeal (){
Meal meal = new Meal();
meal.addItem(new ChickenBurger());
meal.addItem(new Pepsi());
return meal;
}
}
//7.BuiderPatternDemo 使用 MealBuilder 来演示建造者模式(Builder Pattern)
public class BuilderPatternDemo {
public static void main(String[] args) {
MealBuilder mealBuilder = new MealBuilder();
Meal vegMeal = mealBuilder.prepareVegMeal();
System.out.println("Veg Meal");
vegMeal.showItems();
System.out.println("Total Cost: " +vegMeal.getCost());
Meal nonVegMeal = mealBuilder.prepareNonVegMeal();
System.out.println("\n\nNon-Veg Meal");
nonVegMeal.showItems();
System.out.println("Total Cost: " +nonVegMeal.getCost());
}
}
/*
输出结果:
Veg Meal
Item : Veg Burger, Packing : Wrapper, Price : 25.0
Item : Coke, Packing : Bottle, Price : 30.0
Total Cost: 55.0
Non-Veg Meal
Item : Chicken Burger, Packing : Wrapper, Price : 50.5
Item : Pepsi, Packing : Bottle, Price : 35.0
Total Cost: 85.5
*/
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/181067.html