常用的设计模式
一、单例模式:
- 1、单例类只能有一个实例。
- 2、单例类必须自己创建自己的唯一实例。
- 3、单例类必须给所有其他对象提供这一实例。
优点:
- 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
- 1、要求生产唯一序列号。
- 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
单例模式的几种实现方式
1.饿汉式(线程安全,调用效率高,但是,不能延时加载)
// 饿汉式单例 public class Singleton1 { // 指向自己实例的私有静态引用,主动创建 private static Singleton1 singleton1 = new Singleton1(); // 私有的构造方法 private Singleton1(){} // 以自己实例为返回值的静态的公有方法,静态工厂方法 public static Singleton1 getSingleton1(){ return singleton1; } }
我们知道,类加载的方式是按需加载,且加载一次。。因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
2.懒汉式(线程安全,调用效率低,但是,可以延时加载)
// 懒汉式单例 public class Singleton2 { // 指向自己实例的私有静态引用 private static Singleton2 singleton2; // 私有的构造方法 private Singleton2(){} // 以自己实例为返回值的静态的公有方法,静态工厂方法 public static Singleton2 getSingleton2(){ // 被动创建,在真正需要使用时才去创建 if (singleton2 == null) { singleton2 = new Singleton2(); } return singleton2; } }
我们从懒汉式单例可以看到,单例实例被延迟加载,即只有在真正使用的时候才会实例化一个对象并交给自己的引用。
这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
3.双重检测锁式(由于JVM底层内部模型原因,偶尔会出现问题。不建议使用)
public class Singleton { private static Singleton instance; //程序运行时创建一个静态只读的进程辅助对象 private static readonly object syncRoot = new object(); private Singleton() { } public static Singleton GetInstance() { //先判断是否存在,不存在再加锁处理 if (instance == null) { //在同一个时刻加了锁的那部分程序只有一个线程可以进入 lock (syncRoot) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。
使用双重检测同步延迟加载去创建单例的做法是一个非常优秀的做法,其不但保证了单例,而且切实提高了程序运行效率
优点:线程安全;延迟加载;效率较高。
4.静态内部类式(线程安全,调用效率高,可以延时加载)
public sealed class Singleton{ private static class SingletonInstance{ //在第一次引用类的任何成员时创建实例,公共语言运行库负责处理变量初始化 private static final Singleton instance=new Singleton(); } private Singleton() { } public static Singleton getInstance(){ return SingletonInstance.instance; } }
外部类没有static属性,则不会像饿汉式那样立即加载对象。
只有真正调用getInstance(),才会加载静态内部类。加载类时是线程安全的。instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性。
兼备了并发高效调用和延迟加载的优势!
5.枚举实现单例模式
public enum Singleton{ //定义一个枚举元素,它就代表了Singleton的一个实例 INSTANCE; //单例可以有自己的操作 public void singletonOperation(){ //功能处理(可额外添加需要的操作) } }
优点:实现简单
枚举本身就是单例模式。由于JVM根本上提供保障,避免通过反射和反序列化的漏洞
缺点:无延迟加载
二、工厂模式
实现了创建者和调用者的分离。
分类:简单工厂模式、工厂方法模式、抽象工厂模式
面向对象设计的基本原则
OCP(开闭原则):一个软件的实体应当对扩展开放,对修改关闭。
DIP(依赖倒转原则):要针对接口编程,不要针对实现编程。
LOD(迪米特法则):只与你直接的朋友通信,而避免和陌生人通信。
工厂核心本质:
实例化对象,用工厂方法代替new操作,将选择实现类、创建对象统一管理和控制,从而将调用者跟我们的实现类解耦。
简单工厂模式
我们将创建一个 Shape 接口
public interface Shape { void draw(); }
实现 Shape 接口的实体类
Rectangle.java
public class Rectangle implements Shape { @Override public void draw() { System.out.println("Inside Rectangle::draw() method."); } }
Square.java
public class Square implements Shape { @Override public void draw() { System.out.println("Inside Square::draw() method."); }
Circle.java
public class Circle implements Shape { @Override public void draw() { System.out.println("Inside Circle::draw() method."); } }
创建一个工厂,生成基于给定信息的实体类的对象。
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; } }
使用该工厂,通过传递类型信息来获取实体类的对象。
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.
二、工厂方法模式
public interface IFactory { ICar CreateCar(); }
创建抽象产品
public interface ICar { void GetCar(); }
创建具体工厂代码:
// 具体工厂类: 用于创建跑车类 public class SportFactory : IFactory { public ICar CreateCar() { return new SportCar(); } } // 具体工厂类: 用于创建越野车类 public class JeepFactory : IFactory { public ICar CreateCar() { return new JeepCar(); } } // 具体工厂类: 用于创建两厢车类 public class HatchbackFactory : IFactory { public ICar CreateCar() { return new HatchbackCar(); } }
创建具体产品代码:
// 具体产品类: 跑车 public class SportCar : ICar { public void GetCar() { System.out.println("跑车"); } } // 具体产品类: 越野车 public class JeepCar : ICar { public void GetCar() { System.out.println("越野车"); } } // 具体产品类: 两箱车 public class HatchbackCar : ICar { public void GetCar() { System.out.println("两箱车"); } }
创建客户端代码:
class Client{ public static void main(string[] args){ ICar c1 = new SportFactory.CreateCar();
ICar c2 = new JeepFactory .CreateCar();
ICar c3 = new HatchbackFactory .CreateCar();
c1.GetCar();
c2.GetCar();
c3.GetCar(); } }
工厂方法的优点/缺点:
- 优点:
- 子类提供挂钩。基类为工厂方法提供缺省实现,子类可以重写新的实现,也可以继承父类的实现。-- 加一层间接性,增加了灵活性
- 屏蔽产品类。产品类的实现如何变化,调用者都不需要关心,只需关心产品的接口,只要接口保持不变,系统中的上层模块就不会发生变化。
- 典型的解耦框架。高层模块只需要知道产品的抽象类,其他的实现类都不需要关心,符合迪米特法则,符合依赖倒置原则,符合里氏替换原则。
- 多态性:客户代码可以做到与特定应用无关,适用于任何实体类。
- 缺点:需要Creator和相应的子类作为factory method的载体,如果应用模型确实需要creator和子类存在,则很好;否则的话,需要增加一个类层次。(不过说这个缺点好像有点吹毛求疵了)
代理模式:
https://www.cnblogs.com/yueshutong/p/9500632.html#2926676091