# design-mode **Repository Path**: SilenceCJSN/design-mode ## Basic Information - **Project Name**: design-mode - **Description**: 工厂模式、抽象工厂模式、单例模式、建造者模式、享元模式、原型模式、装饰器模式、适配器模式、门面模式、策略模式、模板方法模式 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2021-12-10 - **Last Updated**: 2022-02-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # design pattern 设计模式 ## factory-pattern 工厂模式 适合生产流程不复杂的过程,何为不复杂? 生产一个对象所需要的工厂(模块)不多时。 ## abstract-factory-pattern 抽象工厂模式 与工厂模式的区别在于,抽象工厂模式将抽象工厂再向上抽象一层, 出现这种情况是因为,假如,我要生产能连接mysql数据库且能输入命令操作的 数据库工具,那么我不仅要有连接接口还要有命令接口;这两个接口,如果不用 一个上层抽象封装的话,以后使用mysql数据库工具时,还需要用两个工厂去生产这 下属的两个接口。当实现一个大功能所下属的子功能模块(工厂)很多的时候,如果 一个一个去找子功能模块(工厂)的具体实现类,那就十分复杂了。 最好的操作还是抽象,将这个大功能进行抽象(形成公司),它封装了许许多多的子功能模块 抽象(工厂)的对象。 ## singleton-pattern 单例设计模式 保证一个类只有一个实例,并提供一个全局访问方法。 懒汉与饿汉的差别就在于实例对象的初始化节点:懒汉模式是在访问了提供全局拿到对象的方法时, 进行初始化;饿汉是类加载时就初始化对象。 懒汉模式考虑多线程进行创建对象,需要加锁。 反射攻击私有构造函数实例化对象,破解懒汉单例模式。 通过序列化将实体对象写入文件,再反序列化出来,仍是同一个对象。实现这个功能, 通过看Serializable的文档能知道,如果要从流中反序列化对象,就必须实现 Object readResolve() throws ObjectStreamException方法;同时每一个序列化类 都伴随着一个版本号serialVersionUID,这个uid帮助反序列化比较是否该类发生了版本改变(uid改变), 改变就会抛出uid不同的异常;所以我们必须在序列化的类中定义一个版本号static final long serialVersionUID = 42L ## builder-pattern 建造者模式 有一类制作流程复杂的产品需要创建,创建它的步骤非常多,通过一个构造者接口来提供生产产品零件的各个方法, 并且提供一个获取该产品对象的方法。使用一个中介者来负责客户与厂家的产品生产沟通,客户把产品的大致信息描绘给中介者, 中介者对象有一个属性是厂家接口,通过这个厂家接口属性就能创建符合客户要求的零件,并组装成产品返回给客户。 当复杂产品中的属性都是final修饰的时候,可以去掉中介者;客户直接与厂家沟通。 ## prototype-pattern 原型模式 当需要拷贝复杂对象时,对象中包含了引用对象,我们需要在拷贝的同时把引用对象拷贝一份,赋给克隆对象中的被引用对象。 ![原型模式.png](picture/prototype.png) 重写clone方法时,需要看该类的属性是否是可变的,如果不是可变属性,可以使用浅拷贝。 ## decorator-pattern 装饰器模式 在不对原有对象进行修改的情况下,添加新功能。使用一个顶层接口,所有的功能和装饰器都得实现它, 这样就用多态把所有的功能关联起来,当我们有一个原本的功能A,我们打算新增功能B和C,我们只需要 新增一个抽象装饰器实现功能接口,这个装饰器有功能对象作为属性,新增装饰器的子类B和C,在实现方法 时调用原有方法,并新增方法实现。最后通过创建B和C实例并传入功能接口对象,就是进行了功能扩展。 ![Snipaste_2019-12-16_11-02-21.png](picture/decorator.png) ## adapter-pattern 适配器模式 比如我有一个三孔插座,但是手机充电器是两个孔的,我想要给手机充电,需要一个适配器来把这个三孔插座 转化为双孔插座。适配器模式的功能就是把原本因为接口不兼容而不能一起工作的来进行转换而能进行一起工作。 ![adapter1.png](picture/adapter1.png) ![adapter2.png](picture/adapter2.png) 需要注意类加载和对象加载适配器的区别。类加载(通过继承被适配类)会导致,违反最少知道原则,适配器 能调用被适配类的方法 ## flyweight-pattern 享元模式 在flyweight-pattern模块代码中模拟了游戏中在地图上种树。一款游戏,有很多场景,而且都需要树来作为背景; 但是有一点树的种类就那几种,不会很多。那么我们要想办法把各种树给放起来,地图中哪个点要用到这个树就拿去用。 我在代码演示中结合了单例设计模式来展示,地图中出现的树数据。 ## strategy-pattern 策略模式 假如我们需要从上海去北京,我们的起点和目的城市是不变的,其中用什么方式去这不是我们关心的。我们希望 能复用一部分的过程,对此我们可以用策略模式,当我们有多种策略时,解决这个路程问题可以方便替换。 转化到Java中是:当我们在不断扩展类的时候,想用某些类现成写好的方法,那么就要不断继承;这样实现复用的话, 会在不断扩展中导致类关系变得复杂。这会影响代码的可读性和健壮性。 ![strategy1.png](picture/strategy1.png) ![strategy2.png](picture/strategy2.png) # 里氏替换原则(Liskov-Substitution-Principle) 里氏替换原则:所有引用基类的地方必须能透明地使用其子类的对象。 1. 子类必须完全实现父类的方法 如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。 2. 子类可以有自己的个性 3. 覆盖或实现父类的方法时输入参数可以被放大(input_params_plus包下) 4. 覆写或实现父类的方法时输出结果可以被缩小 父类的一个方法的返回值是一个类型T,子类的相同方法(重载或覆写)的返回值为S,那么里氏替换原则就要求S必须小于等于T ,也就是说,要么S和T是同一个类型,要么S是T的子类,为什么呢?分两种情况,如果是覆写,父类和子类的同名方法的输入参数是相同 的,两个方法的范围值S小于等于T,这是覆写的要求,这才是重中之重,子类覆写父类的方法,天经地义。如果是重载,则要求方法的输 入参数类型或数量不相同,在里氏替换原则要求下,就是子类的输入参数宽于或等于父类的输入参数,也就是说你写的这个方法是不会被调用的,参考上面讲的前置条件。 采用里氏替换原则的目的就是增强程序的健壮性,版本升级时也可以保持非常好的兼容性。即使增加子类,原有的子类还可以继续运行。在实际项目中, 每个子类对应不同的业务含义,使用父类作为参数,传递不同的子类完成不同的业务逻辑,非常完美! 在项目中,采用里氏替换原则时,尽量避免子类的“个性”,一旦子类有“个性”,这个子类和父类之间的关系就很难调和了,把子类当做父类使用, 子类的“个性”被抹杀——委屈了点;把子类单独作为一个业务来使用,则会让代码间的耦合关系变得扑朔迷离——缺乏类替换的标准。 # 依赖倒置原则dependence-inversion-principle - 高层模块不应该依赖底层模块,两者都应该依赖其抽象 - 抽象不应该依赖细节 - 细节应该依赖抽象 依赖倒置原则在Java语言中的表现是: - 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的; - 接口或抽象类不依赖于实现类; - 实现类依赖接口或抽象类。 依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合,我们怎么在项目中 使用这个规则呢?只要遵循以下的几个规则就可以: - 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备。这是依赖倒置的基本要求,接口和抽象类都是属于抽象的,有了抽象才可能依赖倒置。 - 变量的表面类型尽量是接口或者是抽象类 - 任何类都不应该从具体类派生 - 尽量不要覆写基类的方法 - 结合里氏替换原则使用 接口负责定义public属性和方法,并且声明与其他对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。 讲了这么多,估计大家对“倒置”这个词还是有点不理解,那到底什么是“倒置”呢?我们先说“正置”是什么意思,依赖正置就是类间的依赖是实实在在的实现类间的依赖, 也就是面向实现编程,这也是正常人的思维方式,我要开奔驰车就依赖奔驰车,我要使用笔记本电脑就直接依赖笔记本电脑,而编写程序需要的是对现实世界的事物进行抽象, 抽象的结果就是有了抽象类和接口,然后我们根据系统设计的需要产生了抽象间的依赖,代替了人们传统思维中的事物间的依赖,“倒置”就是从这里产生的。 依赖倒置原则是6个设计原则中最难以实现的原则,它是实现开闭原则的重要途径,依赖倒置原则没有实现,就别想实现对扩展开放,对修改关闭。在项目中, 大家只要记住是“面向接口编程”就基本上抓住了依赖倒置原则的核心。 # 接口隔离原则Interface isolation principle 接口隔离原则与单一职责的审视角度是不相同的,单一职责要求的是类和接口职责单一,注重的是职责,这是业务逻辑上的划分,而接口隔离原则要求接口的方法尽量少。 例如一个接口的职责可能包含10个方法,这10个方法都放在一个接口中,并且提供给多个模块访问,各个模块按照规定的权限来访问,在系统外通过文档约束“不使用的方 法不要访问”,按照单一职责原则是允许的,按照接口隔离原则是不允许的,因为它要求“尽量使用多个专门的接口”。专门的接口指什么?就是指提供给每个模块的都应该 是单一接口,提供给几个模块就应该有几个接口,而不是建立一个庞大的臃肿的接口,容纳所有的客户端访问。 接口隔离原则是对接口的定义,同时也是对类的定义,接口和类尽量使用原子接口或原子类来组装。但是,这个原子该怎么划分是设计模式中的一大难题, 在实践中可以根据以下几个规则来衡量: - 一个接口只服务于一个子模块或业务逻辑 - 通过业务逻辑压缩接口中的public方法,接口时常去回顾,尽量让接口达到“满身筋骨肉”,而不是“肥嘟嘟”的一大堆方法 - 已经被污染的接口,尽量去修改,若变更的风险较大,则采用适配器模式进行转化处理 - 了解环境,拒绝盲从。 # 迪米特法则的定义(Law of Demeter,LoD) 又叫最少知道原则,描述的是:一个对象应该对其他对象有最少的了解。通俗的讲,一个类应该对自己需要耦合或调用的类知道得最少, 你(被耦合或调用的类)的内部是如何复杂都和我没关系。 v1包下,模拟了老师让体育委员点名。 v2包下,模拟了软件的安装过程。 迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高。其要求的结果就是产生了大量的中转或跳转类, 导致系统的复杂性提高,同时也为维护带来了难度。读者在采用迪米特法则时需要反复权衡,既做到让结构清晰,又做到高内聚低耦合。 # 开闭原则 一个软件实体如类、模块和函数应该对外扩展开放,对修改关闭。