zoukankan      html  css  js  c++  java
  • 懂一点设计模式

    零、参考资料

    原文地址
    一直认为,设计模式没啥卵用,不需要学。
    设计模式是设计的模式,它妄想通过总结提炼优秀设计的常有特点来达到实现优秀设计的目的。
    先有符合设计模式的代码,然后才有设计模式。设计模式是对工整代码的总结提炼。
    设计模式的本质是:面向接口编程,大量使用接口,使得扩展性强。
    设计模式几乎为每一种良好的代码设计都起了一个名字,几乎顺手一写不经意间都能写出设计模式。
    有些人自己吃了屎,感觉很不爽,非要叫全天下的人一起吃屎,他们才能感到平衡,比如那些使用vim的和使用php的。。。

    一、设计模式分类

    设计模式共有23种,总体来说设计模式分为三大类:

    • 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
    • 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
    • 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

    二、设计模式六大原则

    1. 开闭原则(Open Close Principle)
      开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
    2. 里氏代换原则(Liskov Substitution Principle)
      里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科
    3. 依赖倒转原则(Dependence Inversion Principle)
      这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。
    4. 接口隔离原则(Interface Segregation Principle)
      这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
    5. 迪米特法则(最少知道原则)(Demeter Principle)
      为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
    6. 合成复用原则(Composite Reuse Principle)
      原则是尽量使用合成/聚合的方式,而不是使用继承。

    三、工厂模式(Factory Method)

    工厂模式就是一个工厂可以new出来好几种类。它的好处就是入口统一。当人们在学习一个库的时候, 如果有一个统一的入口,就可以做到提纲挈领的效果。
    比如一个Animal工厂,可以生产Dog、Cat两种Animal

    class Animal:
        pass
    
    class Dog(Animal):
        pass
    
    class Cat(Animal):
        pass
    

    工厂模式的实现有三种写法:

    • 普通工厂模式(传字符串)
    class AnimalFactory:
        @staticmethod
        def newAnimal(what: str) -> Animal:
            if what == "dog":
                return Dog()
            elif what == "cat":
                return Cat()
    
    
    dog = AnimalFactory.newAnimal("dog")
    cat = AnimalFactory.newAnimal("cat")
    
    • 多个工厂模式(写成多个函数)
    class AnimalFactory:
        def newDog(self) -> Animal:
            return Dog()
    
        def newCat(self) -> Animal:
            return Cat()
    
    
    factory = AnimalFactory()
    dog = factory.newDog()
    cat = factory.newCat()
    
    • 静态工厂模式
    class AnimalFactory:
        @staticmethod
        def newDog() -> Animal:
            return Dog()
    
        @staticmethod
        def newCat() -> Animal:
            return Cat()
    
    
    dog = AnimalFactory.newDog()
    cat = AnimalFactory.newCat()
    

    四、抽象工厂模式(Abstract Factory)

    抽象工厂模式是为Dog、Cat各建立一个工厂,让这两个工厂实现工厂接口

    class AnimalFactory:
        def newAnimal(self):
            pass
    
    class DogFactory(AnimalFactory):
        def newAnimal(self) -> Dog:
            return Dog()
    
    class CatFactory(AnimalFactory):
        def newAnimal(self) -> Cat:
            return Cat()
    
    animalFactory = DogFactory()  # 这个地方可以很容易把DogFactory改成CatFactory而不需要更改其它地方
    dog = animalFactory.newAnimal()
    

    五、单例模式(Singleton)

    单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:

    1. 某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
    2. 省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
    3. 有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
    public class Singleton {  
      
        /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */  
        private static Singleton instance = null;  
      
        /* 私有构造方法,防止被实例化 */  
        private Singleton() {  
        }  
        /* 静态工程方法,创建实例 */  
        public static Singleton getInstance() {  
            if (instance == null) {  
                instance = new Singleton();  
            }  
            return instance;  
        }  
    }
    

    单例模式一旦设计并发,就会变得有些繁琐。如果不求行,直接看下节。再说了,就算实例被多创建了几次,又有什么关系。
    给getInstance()函数加个锁

    public static synchronized Singleton getInstance() {  
            if (instance == null) {  
                instance = new Singleton();  
            }  
            return instance;  
    }  
    

    给函数加锁有点笨重

    public static Singleton getInstance() {
    		if (instance == null) {
    			synchronized (instance) {
    				if (instance == null) {
    					instance = new Singleton();
    				}
    			}
    		}
    		return instance;
    }
    

    为啥用单例类而不是一个类中有很多静态方法?

    • 首先,静态类不能实现接口。(从类的角度说是可以的,但是那样就破坏了静态了。因为接口中不允许有static修饰的方法,所以即使实现了也是非静态的)
    • 其次,单例可以被延迟初始化,静态类一般在第一次加载是初始化。之所以延迟加载,是因为有些类比较庞大,所以延迟加载有助于提升性能。
    • 再次,单例类可以被继承,他的方法可以被覆写。但是静态类内部方法都是static,无法被覆写。
    • 最后一点,单例类比较灵活,毕竟从实现上只是一个普通的Java类,只要满足单例的基本需求,你可以在里面随心所欲的实现一些其它功能,但是静态类不行。

    而我觉得,单例写起来简单,不用每个函数前面都加个static

    六、建造者模式

    跟工厂模式一样,只不过它产生的是一个列表

    public class Builder {  
        private List<Sender> list = new ArrayList<Sender>();  
          
        public void produceMailSender(int count){  
            for(int i=0; i<count; i++){  
                list.add(new MailSender());  
            }  
        }  
          
        public void produceSmsSender(int count){  
            for(int i=0; i<count; i++){  
                list.add(new SmsSender());  
            }  
        }  
    }  
    public class Test {  
        public static void main(String[] args) {  
            Builder builder = new Builder();  
            builder.produceMailSender(10);  
        }  
    }  
    

    七、原型模式(Prototype)

    原型模式就是实例之间相互独立,没有共同引用的数据。这可能会涉及到深复制、浅复制问题
    Java中深复制是通过写入读出ObjectStream来实现的

    public Object deepClone() throws IOException, ClassNotFoundException {  
      
            /* 写入当前对象的二进制流 */  
            ByteArrayOutputStream bos = new ByteArrayOutputStream();  
            ObjectOutputStream oos = new ObjectOutputStream(bos);  
            oos.writeObject(this);  
      
            /* 读出二进制流产生的新对象 */  
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());  
            ObjectInputStream ois = new ObjectInputStream(bis);  
            return ois.readObject();  
        }  
    

    八、适配器模式

    适配器模式其实相当于一种映射,可以使两套不同的体系互相兼容。
    一个事物有两种描述方式,这两种描述方式之间的沟通就需要适配器模式。
    适配器模式有三种形式:

    • 类的适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
      使用场景:使用接口A来引用类B
      方法:编写类C implement A extends B
    • 对象的适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法就行。
      其实就是包装一下
    • 接口的适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可
      例如Swing中的MouseAdapter它实现了MouseListener接口的全部方法。

    九、装饰器模式(Decorator)

    装饰器对原有类封装一下,在函数调用前、调用后、抛出异常后可以进行一些处理。
    此模式在AOP(面向切面编程)中大量应用。
    Java中实现AOP挺麻烦,必须要新建一个类,根本原因在于Java中没有函数指针的概念;Python就简单多了、直管多了。

    十、代理模式(Proxy)

    代理模式对原有类封装一下,代理类调用原有类的方法并对产生的结果进行转化
    跟装饰器模式容易弄混,装饰器模式比代理模式更轻。装饰器类在编程时是用不到,它只是潜在地封装了一下原有类。

    十一、外观模式(Facade)

    集中调用原则,尽量减少类与类之间的依赖关系

    十二、桥接模式(Bridge)

    接口的完美使用,最经典的应用就是JDBC,它下面可以挂MySQL、Oracle等各种数据库实现。

    十三、组合模式(Composite)

    TreeNode和Tree,多个对象之间互相引用

    十四、享元模式(Flyweight)

    数据库连接池、套接字连接池

    十五、策略模式

    一个游戏的AI实现有两种实现思路:SearchAI是搜索法实现的AI,TableAI是打表法实现的AI,它们都实现了AI接口,这样就可以方便地进行策略切换。
    一言以蔽之,策略模式是接口的一种应用。

    十六、模板方法模式

    抽象类实现一个主要方法,这个主要方法调用抽象类的其它抽象方法。派生类继承抽象类并实现它的所有抽象方法。
    这样一来,就相当于抽象类给派生类规定了一个函数模板。
    应用:例如HttpServlet,我们需要实现doGet()和doPost()方法
    冰山十分之九藏在海面以下,我们只能看到十分之一。

    十七、观察者模式

    Java Swing的事件机制大量使用观察者模式,各个Listener都是订阅者。

    十八、迭代器模式

    实现size(),get(index)等函数

    十九、责任链模式

    流水线工程
    在Tomcat中,每一个请求的处理都要经过一条链

    二十、命令模式

    实现命令发出者和命令执行者之间的解耦
    比如一个命令难以用一个函数实现,需要用一个类来实现。

    二十一、备忘录模式

    备份恢复模式,专门实现一个类记录当前类的状态。当前类的状态可以load或者save到备忘录类中。

  • 相关阅读:
    pyexharts教程
    elasticsearch常用查询语句
    kubelet连接apiserver报TLS错误
    k8s init.yaml
    bareapi nginx配置
    traefik配置https
    kubernetes中通过static pod部署elasticsearch生产集群
    聊天服务器架构
    使用JAX-RS的实现Jersey创建RESTful Web服务
    SpringBoot+Thymeleaf+MyBatis 实现RESTful API
  • 原文地址:https://www.cnblogs.com/weiyinfu/p/6476809.html
Copyright © 2011-2022 走看看