常见的设计模式

2020-01-15  本文已影响0人  竖起大拇指

在开始之前,我们先讲下面向对象设计模式的六大原则

单一职责原则

就一个类而言,应该仅有一个引起它变化的原因。简单来说,一个类中应该是一组相关性很高的函数,数据的封装。

我们在App中往往会用到很多的公共方法,比如获取系统的时间,这个功能可能在App中的很多地方都要用到。这个时候我们一般会单独写个工具类,把处理时间相关的方法都放到这个类里面,这样就能减少重复代码,App的结构会更加清晰。当需要添加其他跟时间相关的方法时,就可以都加到这个TimeUtils类里面,这就是我们平时遵循的单一职责原则。

开闭原则

软件中的对象(类,模块,函数等),应该是对于扩展是开放的,对于修改是封闭的。

我们在软件开发过程中就要考虑到后续的扩展和修改。比如说,我们在开发一款类似于universal-image-loader的图片加载框架,可能一开始我们的功能比较简单,图片缓存只有内存缓存。当我们新版本需要添加SD卡缓存时,就要注意尽可能的减少对原来代码的修改,因为这样很可能会引入新的bug。而要做到开闭原则,一般有2中途径,一是通过继承原有的类;二是通过抽象和接口。

里式替换原则

所有引用基类的地方必须能透明的使用其子类。通俗的说,就是只要父类能出现的地方子类就可以出现,而且替换为子类以后不会出现任何错误或者异常。反过来就不行了,子类出现的地方父类不一定能适应。

要实现里氏替换原则,一般需要一个抽象的父类,父类中定义了子类的公共方法,子类继承或是实现父类以后扩展不同的功能,这样一来可以实现根据不同的需要来应用对应的子类,从而达到应用不同的功能的目的,程序的扩展性大大增强。同时这也体现了开闭原则,即当需要增加新功能时,只要继承或实现父类,实现新增的功能就达到了扩展的目的,而不是直接修改原来的代码,也即对扩展开放,对修改封闭。

依赖倒置原则

依赖倒置原则在Java中的表现就是:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或者抽象类产生的。

一句话就是依赖抽象而不依赖具体的实现。比如上面我们说开发一个图片加载框架,那我们肯定会根据单一职责原则划分不同的模块,比如网络加载模块,图片缓存模块,以及主模块等。主模块肯定会调用图片缓存模块,如果我们调用的是图片缓存模块的具体实现,那么当我们修改图片模块时就很可能需要对应修改主模块,这就是耦合了,一个比较好的做法是将图片缓存模块抽象出来,而主模块调用这个抽象即可,这样也就是依赖抽象了。

接口隔离原则

类间的依赖关系应该建立在最小的接口上。

接口隔离原则就是让客户端依赖的接口尽可能的小。就是在上面提到的依赖倒置(依赖抽象而不是实现)原则的基础上,增加一个最小化依赖的原则。说白了就是在依赖接口的基础上依赖尽可能小的接口。

迪米特原则(又称最少知道原则)

一个对象应该对其他的对象有最小的了解。
通俗的讲,一个类应该对自己需要耦合或者调用的类知道得最小,调用者或者依赖者只要知道它需要的方法即可。要做到这个原则,需要我们对各个模块之间的功能进行很好的区分和分配,把相互之间的依赖和耦合减小到最小。

1.单例模式

所谓的单例设计指的是一个类只允许产生一个实例化对象。是最好理解的一种设计模式,分为懒汉式和饿汉式。
饿汉式:构造方法私有化,外部无法产生新的实例化对象,只能通过static方法获取到实例化对象。

public class Singleton {
    //饿汉式
    public static Singleton singleton=new Singleton();
  //private 声明构造方法
    private Singleton(){

    }
    public static Singleton  getInstance(){
        return singleton;
    }
}

//Kotlin实现
object Singleton

线程安全的懒汉式:当第一次去使用Singleton对象的时候才会为其产生实例化对象的操作。

//Java实现
public class SingletonDemo {
    private static SingletonDemo instance;
    private SingletonDemo(){}
    public static synchronized SingletonDemo getInstance(){//使用同步锁
        if(instance==null){
            instance=new SingletonDemo();
        }
        return instance;
    }
}

//Kotlin实现
class SingletonDemo private constructor() {
    companion object {
        private var instance: SingletonDemo? = null
            get() {
                if (field == null) {
                    field = SingletonDemo()
                }
                return field
            }
        @Synchronized
        fun get(): SingletonDemo{
            return instance!!
        }
    }

}

大家都知道在使用懒汉式会出现线程安全的问题,需要使用使用同步锁,在Kotlin中,如果你需要将方法声明为同步,需要添加@Synchronized注解。

双重校验锁式:

public class LazySingleton {
  //volatile保证了:1 instance在多线程并发的可见性 
      // 2 禁止instance在操作时的指令重排序
    private static volatile LazySingleton instance=null;

    private LazySingleton(){

    }

    public static LazySingleton getInstance(){
        if(instance==null){ //第一次判空 保证不必要的同步
            synchronized (LazySingleton.class){// synchronized对Singleton加全局锁,保证每次只要一个线程创建实例
                if(instance==null){//第二次判空为了在null的情况下创建实列
                    instance=new LazySingleton();
                }
            }
        }
        return instance;
    }
}

//kotlin实现
class LazySingleton private constructor() {
    companion object {
        val instance: LazySingleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
        LazySingleton () }
    }
}

当多个线程并发执行getInstance方法时,懒汉式会存在线程安全的问题,所以用到了synchronized来实现线程的同步,当一个线程获得锁的时候其他线程就只能再外等待其执行完毕。而饿汉式不存在线程安全的问题。

静态内部类

    public class Singleton{

      private static class SingletonHolder{
            private staitc final Singleton INSTANCE=new Singleton();
        }

          private Singleton(){
        }
  
          public static Singleton getInstance(){
              return SingletonHolder.INSTANCE;
        }

}

//kotlin实现
class Singleton private constructor() {
    companion object {
        val instance = SingletonHolder.holder
    }

    private object SingletonHolder {
        val holder= Singleton()
    }

}


相比饿汉式,这种方式即使加载了Singleton类,也不会创建Singleton实例,因为Singleton的静态变量放到了内部类中,只有内部类被加载了,Singleton实例才会被创建。

枚举

  public enum Singleton{
    INSTANCE;
      public static Singleton getInstance(){
          return INSTANCE;
      }
}

反编译之后的代码:

public final class Singleton extends java.lang.Enum<Singleton>{
    public staitc final Singleton INSTANCE;
    staitc{
        INSTANCE=new Singleton();
    }

     public static Singleton getInstance(){
        
        return INSTANCE;
      }
}

类加载的逻辑位于synchronized代码块中,是线程安全的,而饿汉式,静态内部类已经枚举方式实现的单例实例化都处于类加载时机,所以它们都是线程安全的。

2.工厂设计模式

工厂模式分为工厂方法模式和抽象工厂模式

工厂方法模式分为三种:
1.普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建
2.多个工厂方法模式,是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。
3.静态工厂方法模式,将上面的多个工厂方法模式的方法设置为静态的,不需要创建实例,直接调用即可。

2.1 普通工厂模式

建立一个工厂类,对实现了同一接口的一些类进行实例的创建。

public interface Sender {
    void Send();
}

public class MailSender implements Sender {
    @Override
    public void Send() {
        System.out.println("This is mail sender ...");
    }
}

public class SmsSender implements Sender {
    @Override
    public void Send() {
        System.out.println("This is sms sender ...");
    }
}

public class FactoryPattern {
    public static void main(String[] args) {
        Sender sender=produce("mail");
    }
    public static Sender produce(String str){
        if("mail".equals(str)){
            return new MailSender();
        }else if("sms".equals(str)){
            return new SmsSender();
        }else {
            System.out.println("输入错误...");
            return null;
        }
    }
}

2.2 多个工厂方法模式

该模式是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。

interface Sender {
    void Send();
}

class MailSender implements Sender {

    @Override
    public void Send() {
        System.out.println("This is mail sender...");
    }
}

class SmsSender implements Sender {

    @Override
    public void Send() {
        System.out.println("This is sms sender...");
    }
}

class SendFactory {
    public Sender produceMail() {
        return new MailSender();
    }

    public Sender produceSms() {
        return new SmsSender();
    }
}

public class FactoryPattern {
    public static void main(String[] args) {
        SendFactory factory = new SendFactory();
        Sender sender = factory.produceMail();
        sender.Send();
    }
}

2.3 静态工厂方法模式

将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。

interface Sender {
    void Send();
}

class MailSender implements Sender {

    @Override
    public void Send() {
        System.out.println("This is mail sender...");
    }
}

class SmsSender implements Sender {

    @Override
    public void Send() {
        System.out.println("This is sms sender...");
    }
}

class SendFactory {
    public static Sender produceMail() {
        return new MailSender();
    }

    public static Sender produceSms() {
        return new SmsSender();
    }
}

public class FactoryPattern {
    public static void main(String[] args) {
        Sender sender = SendFactory.produceMail();
        sender.Send();
    }
}

2.4 抽象工厂模式

工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要扩展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?
那么这就用到了抽象工厂模式,创建多个工厂类,这样一旦需要新增新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。

public interface Sender {

    void send();
}

public interface Provider {

    Sender produce();
}

public class MailSender implements Sender {
    @Override
    public void send() {
        System.out.println("This is mail sender...");
    }
}

public class SmsSender implements Sender {
    @Override
    public void send() {
        System.out.println("This is sms sender...");
    }
}

public class SendMailFactory implements Provider {
    @Override
    public Sender produce() {
        return new MailSender();
    }
}

public class SendSmsFactory implements Provider {
    @Override
    public Sender produce() {
        return new SmsSender();
    }
}

3.适配器模式

适配器模式是将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。主要分三类:类的适配器模式,对象的适配器模式,接口的适配器模式。

3.1 类的适配器模式:

public class Source {

    public void method1(){
        System.out.println("This is origin method");
    }
}

public interface Target {

    /**
     * 与原类中的方法相同
     */
    public void method1();

    /**
     * 新类的方法
     */
    public void mehtod2();

}

public class Adapter extends Source implements Target {
    @Override
    public void mehtod2() {
        System.out.println("This is the target method...");
    }
}

public class AdapterPattern {
    public static void main(String[] args) {
        Target target=new Adapter();
        target.method1();
        target.mehtod2();
    }
}

3.2 对象的适配器模式

基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承Source类,而是持有Source类的实例,以达到解决兼容性的问题。

class Source {
    public void method1() {
        System.out.println("This is original method...");
    }
}

interface Targetable {

    /**
     * 与原类中的方法相同
     */
    public void method1();

    /**
     * 新类的方法
     */
    public void method2();
}

class Wrapper implements Targetable {

    private Source source;

    public Wrapper(Source source) {
        super();
        this.source = source;
    }

    @Override
    public void method1() {
        source.method1();
    }

    @Override
    public void method2() {
        System.out.println("This is the targetable method...");
    }
}

public class AdapterPattern {
    public static void main(String[] args) {
        Source source = new Source();
        Targetable targetable = new Wrapper(source);
        targetable.method1();
        targetable.method2();
    }
}

3.3 接口的适配器模式

接口的适配器是这样的:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行。

/**
 * 定义端口接口 ,提供通信服务
 */
public interface Port {
    /**
     * 远程SSH端口为22
     */
    void SSH();

    /**
     * 网络端口80
     */
    void NET();

    /**
     * Tomcat容器8080
     */
    void Tomcat();

    /***
     * MySQL 数据库端口为3306
     */
    void MySQL();
}

public abstract class Wrapper implements Port{

    @Override
    public void SSH() {

    }

    @Override
    public void NET() {

    }

    @Override
    public void Tomcat() {

    }

    @Override
    public void MySQL() {

    }
}


public class Chat extends Wrapper {

    @Override
    public void NET() {
        System.out.println("Hello World...");
    }
}


public class Server extends Wrapper {
    @Override
    public void SSH() {
        super.SSH();
        System.out.println("Connect success");
    }

    @Override
    public void NET() {
        super.NET();
        System.out.println("WWW...");
    }

    @Override
    public void Tomcat() {
        super.Tomcat();
        System.out.println("Tomcat is running...");
    }

    @Override
    public void MySQL() {
        super.MySQL();
        System.out.println("MySQL is running...");
    }
}


public class AdapterPattern {

    static Port chatPort=new Chat();

   static Port serverPort=new Server();

    public static void main(String[] args) {
        chatPort.NET();

        serverPort.SSH();
        serverPort.NET();
        serverPort.Tomcat();
        serverPort.MySQL();
    }
}

4.装饰模式

就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例。
就增加功能来说,装饰模式比生成子类更为灵活。

public interface Shape {

    void draw();
}
/**
 * 实现接口的实体类
 */
public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Shap:Rectangle...");
    }
}

public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Shap:Circle...");
    }
}

/**
 * 创建实现了Shape接口的抽象装饰类
 */
public abstract class ShapDecorator implements Shape {

    protected Shape decoratedShap;

    public ShapDecorator(Shape decoratedShap){
        this.decoratedShap=decoratedShap;
    }

    @Override
    public void draw() {
        decoratedShap.draw();
    }
}

/***
 * 创建扩展自ShapeDecorator类的实体装饰类
 */
public class RedShapDecorator extends ShapDecorator {

    public RedShapDecorator(Shape decoratedShap) {
        super(decoratedShap);
    }

    @Override
    public void draw() {
        super.draw();
        setReeBorder();
    }

    private void setReeBorder(){
        System.out.println("Border Color:Red");
    }
}

public class DecoratorPattern {

    public static void main(String[] args) {
        Shape circle=new Circle();
        Shape redCircle=new RedShapDecorator(new Circle());
        Shape redRectangle=new RedShapDecorator(new Rectangle());

    }
}

5.享元模式

简单描述一下就是一批对象中既有相同的内容也有不同的内容,相同的内容采用共享的方式,不同的内容通过动态传递的方式,来尽量减少对象的产生。

使用场景

最常见的一个场景就是Java JDK里面的String字符串类,因为JVM中有常量池,常量池的实现就是一种享元模式,避免多个相同对象的存在。另外线程池以及很多用到缓冲池的地方也采用了享元模式,比如Integer类中默认缓存了-128-127之间的整数。等

public interface IFlyweight{
  void setDetail(int width,int height);
}

public class FlyweightImpl implements IFlyweighet{
  private Color color;
  private int width;
  private int height;
  @Override
  public void setDetail(int width,int height){
    this.width=width;
    this.height=height;
    System.out.println(toString());
   }
     public FlyweightImpl(Color color){
            this.color=color;
      }
}

public class FlyweightFactory{
  private Map<String,FlywieightImpl> colorFlyweightMap=new HashMap<>()
  //根据名称获取对象
  public FlyweightImpl getFlyweight(String colorName){
      if(colorflyweightMap.containsKey(colorName)){
              return colorFlyweightMap.get(colorName)
      }else{
          Color color=new Color(colorName);
          FlyweightImpl impl=new FlyweightImpl(color);
          colorFlyweightMap.put(colorName,impl);
          return impl;
      }
   }
}

    //Color类
    public class Color{
      private String type;
      public Color(String type){
        this.type=type;
    }
  }

public static void main(String[] args){
  FlyweightFactory factory=new FlyweightFactory();
  IFlyweight flyweight0=factory.getFlyweight("red");
  flyweight0.setDetail(100,200);
  IFlyweight flyweight1=factory.getFlyweight("red");
  flyweight1.setDetail(300,400);
  IFlyweight flyweight2=factory.getFlyweight("green");
  flyweight2.setDetail(500,600);
  System.out.println(flyweight0==flyweight1)
  System.out.println(flyweight0==flyweight2)
}

上面测试类中,我们分别定义了两种颜色的三个对象,由于相同颜色的对象已经被缓存了,所以我们输出的结果如下

true
false

从上面的运行结果中,我们可以看出,flyweight0 与flyweight1 其实是同一个对象,虽然他们的 width 和 height 属性值不一样,但是他们的底层对应的是同一个对象本身。这就是享元模式的核心内容,通过共享变量,来减少对象的产生,从而减少内存的使用。

上一篇下一篇

猜你喜欢

热点阅读