【1】七大原则
① 单一职责原则(Single Responsibility Principle)-每一个类(接口)应该专注于做一件事情。常常可见接口多继承现象。
② 里氏替换原则(Liskov Substitution Principle)-超类存在的地方,子类是可以替换的。
③ 依赖倒置原则(Dependence Inversion Principle)-实现尽量依赖抽象(抽象类或者接口),不依赖具体实现。
④ 接口隔离原则(Interface Segregation Principle)-每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。
⑤ 迪米特法则(Law Of Demeter)/得墨忒耳法则-又叫最少知道原则,一个软件实体应当尽可能少的与其他实体发生相互作用。
⑥ 开闭原则(Open Close Principle)-软件实体面向扩展开放,面向修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等
⑦ 组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP)-尽量使用合成/聚合达到复用,尽量少用继承。原则: 一个类中有另一个类的对象。即多用组合,少用继承。
① 单一职责原则(Single Responsibility Principle)
一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;提高类的可读性,提高系统的可维护性;变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则。
对类来说的,即一个类应该只负责一项职责。如类A 负责两个不同职责:职责1,职责2。当职责1 需求变更而改变A 时,可能造成职责2 执行错误,所以需要将类A 的粒度分解为A1,A2
② 里氏替换原则(Liskov Substitution Principle)
OO 中的继承性的思考和说明
继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。
继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障
问题提出:在编程中,如何正确的使用继承? => 里氏替换原则
里氏替换原则(Liskov Substitution Principle)在1988 年,由麻省理工学院的一位姓里的女士提出的。如果对每个类型为T1 的对象o1,都有类型为T2 的对象o2,使得以T1 定义的所有程序P 在所有的对象o1 都
代换成o2 时,程序P 的行为没有发生变化,那么类型T2 是类型T1 的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象。
里氏替换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立。 如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。
里氏替换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
使用里氏替换原则时需要注意,子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法。运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。
里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖来解决问题。.
③ 依赖倒置原则(Dependence Inversion Principle)
具体依赖抽象,上层依赖下层。该原则说得直白和具体一些就是声明方法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体类型,因为抽象类型可以被它的任何一个子类型所替代。
具体分析如下
高层模块不应该依赖低层模块,二者都应该依赖其抽象
抽象不应该依赖细节,细节应该依赖抽象
依赖倒转(倒置)的中心思想是面向接口编程
依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java 中,抽象指的是接口或抽象类,细节就是具体的实现类
使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完
成
采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,减少并行开发引起的风险,提高代码的可读性和可维护性。
④ 接口隔离原则(Interface Segregation Principle)
建立单一接口,不要建立庞大的接口,尽量细化接口,接口中的方法尽量少。客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。
也就是要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。依赖几个专用的接口要比依赖一个综合的接口更灵活。接口是设计时对外部设定的约定,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
是不是有点单一职责原则那个味道?
⑤ 迪米特法则(Law Of Demeter)
一个对象应该对其他对象保持最少的了解,类与类关系越密切,耦合度越大。
迪米特法则又叫最少知道原则,一个对象应当对其他对象有尽可能少的了解。迪米特法则简单的说就是如何做到”低耦合”,门面模式和调停者模式就是对迪米特法则的践行。
一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
迪米特法则还有个更简单的定义:只与直接的朋友通信。
直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
对比分析如下两个图
迪米特法则的核心是降低类之间的耦合。但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系, 并不是要求完全没有依赖关系
⑥ 开闭原则(Open Close Principle)
开闭原则(Open Closed Principle)是编程中最基础、最重要的设计原则。
开放封闭原则主要体现在对扩展开放、对修改封闭。意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。而不需要对原有系统进行修改。
可以通过模板方法模式和策略模式进行重构,实现对修改封闭,对扩展开放的设计思路。
封装变化,是实现开放封闭原则的重要手段,对于经常发生变化的状态,一般将其封装为一个抽象,但是要拒绝滥用抽象,只将经常变化的部分进行抽象。
⑦ 组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP)
核心是多用组合,少用继承。
优先使用聚合或合成关系复用代码。通过继承来复用代码是面向对象程序设计中被滥用得最多的东西,因为所有的教科书都无一例外的对继承进行了鼓吹从而误导了初学者,类与类之间简单的说有三种关系,Is-A关系、Has-A关系、Use-A关系,分别代表继承、关联和依赖。其中,关联关系根据其关联的强度又可以进一步划分为关联、聚合和合成,但说白了都是Has-A关系,组合聚合复用原则想表达的是优先考虑Has-A关系而不是Is-A关系复用代码。
组合/聚合复用原则可以使系统更加灵活,类与类之间的耦合度降低。一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用。
其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。
需要说明的是,即使在Java的API中也有不少滥用继承的例子,例如Properties类继承了Hashtable类,Stack类继承了Vector类,这些继承明显就是错误的,更好的做法是在Properties类中放置一个Hashtable类型的成员并且将其键和值都设置为字符串来存储数据,而Stack类的设计也应该是在Stack类中放一个Vector对象来存储数据。记住:任何时候都不要继承工具类,工具是可以拥有并可以使用的,而不是拿来继承的。
Aggregation(聚合关系)、Composition(合成关系)属于Association(关联关系),是特殊的Association关联关系。
聚合关系
Aggregation(聚合关系) 是关联关系的一种,是强的关联关系。聚合关系是整体和个体的关系。普通关联关系的两个类处于同一层次上,而聚合关系的两个类处于不同的层次,一个是整体,一个是部分。同时,是一种弱的“拥有”关系。如电脑由CPU、显示器、键盘、内存、硬盘等组成。
组合关系
Composition(组合关系)是关联关系的一种,他体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合。它要求普通的聚合关系中代表整体的对象负责代表部分的对象的生命周期。Composition(组合关系)是一种强的“拥有”关系,体现了严格的部分和整体的关系,部分和整体的生命周期一致。如果A由B组成,表现为A包含有B的全局对象,并且B对象在A创建的时刻创建。
【2】其他思考
① 封装变化
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
即把会变化的部分取出来并封装起来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分。这样代码变化引起的不经意后果变少,系统变得更有弹性。
封装的另外一层意思是-保证安全与隐私。比如封装一个获取数据的方法,外部只能通过该方法获取某种数据。
② 针对接口编程,而不是针对实现编程
针对接口编程的真正意思是针对超类型编程。这里所谓的接口有多个含义,接口是一个概念,也是一种Java的interface构造。你可以在不涉及java interface的情况下,针对接口编程,关键就在多态。利用多态,程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为,不会被绑死在超类型的行为上。
针对超类型编程这句话,可以更明确的说成变量的声明类型应该是超类型,通常是一个抽象类或者是一个接口,如此,只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量。这也意味着,声明类时不用理会以后执行时的真正对象类型。
③ 多用组合,少用继承
④ 为了交互对象之间的松耦合设计而努力
松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化是因为对象之间的互相依赖降到了最低。
⑤ 类应该对扩展开放,对修改关闭
⑥ 要依赖抽象,不要依赖具体类
不能让高层组件依赖低层组件,而且不管高层或低层组件,两者都应该依赖于抽象。所谓高层组件,是由其他低层组件定义其行为的类。
⑦ 最少知识原则(Least Knowledge)
最少知识原则(另外一个名字叫做得墨忒耳法则Law of Demeter)告诉我们要减少对象之间的交互,只留下几个密友。这是说,当你正在设计一个系统,不管是任何对象,你都要注意它所交互的类有哪些,并注意它和这些类是如何交互的。
这个原则希望我们在设计中,不要让太多的类耦合在一起,免得修改系统中的一部分,会影响到其他部分。如果许多类之间相互依赖,那么这个系统就会变成一个易碎的系统,它需要花许多成本维护,也会因为太复杂而不容易被其他人了解。
究竟要怎样才能避免呢?这个原则提供了一些方针,就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法:
该对象本身;
被当做方法的参数而传递进来的对象;
此方法所创建或实例化的任何对象;
对象的任何组件。
// 不推荐这样 public void getTemp(){ Thermometer thermometer=station.getThermometer(); return thermometer.getTemperature(); } // 推荐这样 public void getTemp(){ return station.getTemperature(); }
采用最少知识原则的缺点
虽然这个原则减少了对象之间的依赖,研究显示这回减少软件的维护成本。但是采用这个原则也会导致更多的包装类被制造出来,以处理和其他组件的沟通。这可能会导致复杂度和开发时间的增加,并降低运行时的性能。
⑧ 别找我,我会找你
好莱坞原则简单来讲就是:别调用我们,我们会调用你。
好莱坞原则可以给我们一种防止“依赖腐败”的方法。当高层组件依赖低层组件,而低层组件又依赖高层组件,而高层组件又依赖边侧组件,边侧组件又依赖低层组件时,依赖腐败就发生了。在这种情况下,没有人可以轻易地搞懂系统是如何设计的。
在好莱坞原则之下,我们允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些低层组件。换句话说,高层组件对待低层组件的方式就是“别调用我们,我们会调用你”。
模板方法模式就契合该原则。当我们设计模板方法模式时,我们告诉子类,“不要调用我们,我们会调用你”。
那么低层组件完全不可以调用高层组件的方法吗?并不尽然!
事实上,低层组件在结束时,常常调用从超类中继承来的方法。我们所要做的是,避免让高层和低层组件之间有明显的环状依赖。
好莱坞原则与依赖倒置原则
依赖倒置原则教我们尽量避免使用具体类,而多使用抽象类。而好莱坞原则是用在创建框架或组件上的一种技巧,好让低层组件能够被挂钩进计算中,而且又不会让高层组件依赖低层组件。两者的目标都是在于解耦,但是依赖倒置原则更加注重如何在设计中避免依赖。
好莱坞原则教我们一个技巧,创建一个有弹性的设计,允许低层结构能够互相操作,而又防止其他类太过依赖它们。
⑨ 类应该只有一个改变的理由
也就是单一职责了哦。
【3】那些设计模式
设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案,设计模式(Design pattern)代表了最佳的实践。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
设计模式的本质提高软件的维护性,通用性和扩展性,并降低软件的复杂度。
设计模式并不局限于某种语言,java,php,c++ 都有设计模式。
① 设计模式类型
设计模式分为三种类型,共23 种。
① 创建型模式
单例模式(Singleton)、抽象工厂模式(Abstract Factory)、原型模式(Prototype)、建造者模式(Builder)、工厂模式(Factory Method)。
② 结构型模式
适配器模式(Adapter)、桥接模式(Bridge)、装饰模式(Decorator)、组合模式(Composite)、外观模式(Facade)、享元模式(Flyweight)、代理模式(Proxy)。
③ 行为型模式
模版方法模式(Template Method)、命令模式(Command)、访问者模式(Visitor)、迭代器模式(Iterator)、观察者模式(Observer)、中介者模式(Mediator)、备忘录模式(Memento)、解释器模式(Interpreter)、状态模式(State)、策略模式(Strategy)、职责链模式(责任链模式 Chain of Responsibility)。
设计模式解释
① 单例模式
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
② 工厂模式
将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
三种工厂模式(简单工厂模式、工厂方法模式、抽象工厂模式)。
③ 原型模式
原型模式(Prototype 模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
④ 建造者模式
建造者模式(Builder Pattern) 又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
⑤ 适配器模式
适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)。
主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
⑥ 桥接模式
桥接模式(Bridge 模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
⑦ 装饰者模式
装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)。
⑧ 组合模式
组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象
⑨ 外观模式
外观模式(Facade),也叫“过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节。
⑩ 享元模式
享元模式(Flyweight Pattern) 也叫蝇量模式: 运用共享技术有效地支持大量细粒度的对象。常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象
中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率。
享元模式经典的应用场景就是池技术了,String 常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式。
(11) 代理模式
代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。代理模式有不同的形式, 主要有三种静态代理、动态代理(JDK 代理、接口代理)和Cglib 代理(可以在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。
(12) 模板方法模式
模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),z 在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤.
(13) 命令模式
命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计。
命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
在命名模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。
(14) 访问者模式
访问者模式(Visitor Pattern),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。主要将数据结构与数据操作分离,解决数据结构和操作耦合性问题。
访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口。
访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决。
(15) 迭代器模式
迭代器模式(Iterator Pattern)是常用的设计模式,属于行为型模式
如果我们的集合元素是用不同的方式实现的,有数组,还有java 的集合类,或者还有其他方式,当客户端要遍历这些集合元素的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。
迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底层表示,即:不暴露其内部的结构。
(16) 观察者模式
观察者模式:对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象为Observer,Subject通知Observer 变化,比如这里的奶站是Subject,是1 的一方。用户时Observer,是多的一方。
(17) 中介者模式
中介者模式(Mediator Pattern),用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
中介者模式属于行为型模式,使代码易于维护
(18) 备忘录模式
备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情,或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作。
备忘录模式属于行为型模式。
(19) 解释器模式
在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器
解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)。
(20) 状态模式
状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换。
当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类。
(21) 策略模式
策略模式(Strategy Pattern)中,定义算法族(策略组),分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。
(22) 职责链模式
职责链模式(Chain of Responsibility Pattern), 又叫责任链模式,为请求创建了一个接收者对象的链(简单示意图)。这种模式对请求的发送者和接收者进行解耦。
职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
这种类型的设计模式属于行为型模式。