美文网首页
常见的设计模式

常见的设计模式

作者: 竖起大拇指 | 来源:发表于2020-01-15 17:09 被阅读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 属性值不一样,但是他们的底层对应的是同一个对象本身。这就是享元模式的核心内容,通过共享变量,来减少对象的产生,从而减少内存的使用。

    相关文章

      网友评论

          本文标题:常见的设计模式

          本文链接:https://www.haomeiwen.com/subject/cbsaactx.html