桥接模式:将抽象和实现分离。把实现的对象的指针都放到抽象的类中,使用组合(委托)的方式将抽象和实现彻底地解耦。在抽象的类的方法中调用实现的函数。这样的好处是抽象和实现可以分别独立地变化,系统的耦合性也得到了很好的降低。
类模式的Adapter采用继承的方式复用Adaptee的接口,而在对象模式的Adapter中我们采用组合的方式实现Adaptee的复用。
在Adapter模式的两种模式中,有一个很重要的概念就是接口继承和实现继承的区别和联系。接口继承和实现继承是面向对象领域的两个重要的概念,接口继承指的是通过继承,子类获得了父类的接口,而实现继承指的是通过继承子类获得了父类的实现(并不统共接口)。在C++中的public继承既是接口继承又是实现继承,因为子类在继承了父类后既可以对外提供父类中的接口操作,又可以获得父类的接口实现。当然我们可以通过一定的方式和技术模拟单独的接口继承和实现继承,例如我们可以通过private继承获得实现继承的效果(private继承后,父类中的接口都变为private,当然只能是实现继承了。),通过纯抽象基类模拟接口继承的效果,但是在C++中pure virtual function也可以提供默认实现,因此这是不纯正的接口继承,但是在Java中我们可以interface来获得真正的接口继承了。
在结构图中,ConcreteComponent和Decorator需要有同样的接口,因此ConcreteComponent和Decorator有着一个共同的父类。这里有人会问,让Decorator直接维护一个指向ConcreteComponent引用(指针)不就可以达到同样的效果,答案是肯定并且是否定的。肯定的是你可以通过这种方式实现,否定的是你不要用这种方式实现,因为通过这种方式你就只能为这个特定的ConcreteComponent提供修饰操作了,当有了一个新的ConcreteComponent你又要去新建一个Decorator来实现。但是通过结构图中的ConcreteComponent和Decorator有一个公共基类,就可以利用OO中多态的思想来实现只要是Component型别的对象都可以提供修饰操作的类,这种情况下你就算新建了100个Component型别的类ConcreteComponent,也都可以由Decorator一个类搞定。这也正是Decorator模式的关键和威力所在了。
当然如果你只用给Component型别类添加一种修饰,则Decorator这个基类就不是很必要了。
Composite(组合模式)模式在实现中有一个问题就是要提供对于子节点(Leaf)的管理策略,这里使用的是STL 中的vector,可以提供其他的实现方式,如数组、链表、Hash表等。
Composite模式通过和Decorator模式有着类似的结构图,但是Composite模式旨在构造类(主要使用一个树形结构变量保存),而Decorator模式重在不生成子类即可给对象添加职责(主要是使用对象指针调用对象的方法)。Decorator模式重在修饰,而Composite模式重在表示。
Decorator模式和Proxy模式的相似的地方在于它们都拥有一个指向其他对象的引用(指针),即通过组合的方式来为对象提供更多操作(或者Decorator模式)间接性(Proxy模式)。但是他们的区别是,Proxy模式会提供使用其作为代理的对象一样接口,使用代理类将其操作都委托给Proxy直接进行。这里可以简单理解为组合和委托之间的微妙的区别了。
在OO设计和分析经常有这样一种情况:为了多态,通过父类指针指向其具体子类,但是这就带来另外一个问题,当具体子类要添加新的职责,就必须向其父类添加一个这个职责的抽象接口,否则是通过父类指针是调用不到这个方法了。这样处于高层的父类就承载了太多的特征(方法),并且继承自这个父类的所有子类都不可避免继承了父类的这些接口,但是可能这并不是这个具体子类所需要的。而在Decorator模式提供了一种较好的解决方法,当需要添加一个操作的时候就可以通过Decorator模式来解决,你可以一步步添加新的职责。
Flyweight模式在实现过程中主要是要为共享对象提供一个存放的“仓库”(对象池), 这里是通过C++ STL中Vector容器Flyweight模式在实现过程中主要是要为共享对象提供一个存放的“仓库”(对象池),这里是通过C++ STL中Vector容器.
Facade模式是将各个分步封装成多个类,将这几个类的对象指针放到另外一个综合类中,然后综合类的接口通过这几个类的指针调用这几个类的资源,客户端就直接调用这个综合类就行了。 Proxy模式最大的好处就是实现了逻辑和实现的彻底解耦。Proxy的核心是将远程的对象的指针存到Proxy本地代理中,通过本地代理的方法调用远程对象的指针达到本地操作远程的实现。 Template模式是采用继承的方式实现这一点:将逻辑(算法)框架放在抽象基类中,并定义好细节的接口,子类中实现细节。Template模式实际上就是利用面向对象中多态的概念实现算法实现细节和高层接口的松耦合。可以看到Template模式采取的是继承方式实现这一点的。 Template模式获得一种反向控制结构效果,这也是面向对象系统的分析和设计中一个原则DIP(依赖倒置:Dependency Inversion Principles)。其含义就是父类调用子类的操作(高层模块调用低层模块的操作),低层模块实现高层模块声明的接口。这样控制权在父类(高层模块),低层模块反而要依赖高层模块。
Strategy模式和Template模式要解决的问题是相同(类似)的,都是为了给业务逻辑(算法)具体实现和抽象接口之间的解耦。
Strategy模式和工厂模式类似,将某个父类A(子类有a1,a2)的指针存到另一个类B中(A *sec),在main函数中A sec = new a1,A sec2 = new a2,然后传入类B的构造函数中,再让类B调用方法、通过多态实现对不同子类a1,a2方法的调用。Strategy模式的代码很直观,关键是将算法的逻辑封装到一个类中。
优先使用(对象)组合,而非(类)继承。
继承是一种强制性很强的方式,因此也使得基类和具体子类之间的耦合性很强。而组合(委托)的方式则有很小的耦合性,实现(具体实现)和接口(抽象接口)之间的依赖性很小。
Switch/Case弊端:
1)当状态数目不是很多的时候,Switch/Case可能可以搞定。但是当状态数目很多的时候(实际系统中也正是如此),维护一大组的Switch/Case语句将是一件异常困难并且容易出错的事情。
2)状态逻辑和动作实现没有分离。在很多的系统实现中,动作的实现代码直接写在状态的逻辑当中。这带来的后果就是系统的扩展性和维护得不到保证。
在State模式中我们将状态逻辑和动作实现进行分离,State模式将每一个分支都封装到独立的类中。
State模式在实现中,有两个关键点:
1)将State声明为Context的友元类(friend class),其作用是让State模式访问Context的protected接口ChangeSate()。
2)State及其子类中的操作都将Context*传入作为参数,其主要目的是State类可以通过这个指针调用Context中的方法(在本示例代码中没有体现)。这也是State模式和Strategy模式的最大区别所在。
State模式很好地实现了对象的状态逻辑和动作实现的分离,状态逻辑分布在State的派生类中实现,而动作实现则可以放在Context类中实现(这也是为什么State派生类需要拥有一个指向Context的指针)。这使得两者的变化相互独立,改变State的状态逻辑可以很容易复用Context的动作,也可以在不影响State派生类的前提下创建Context的子类来更改或替换动作实现。 State模式和Strategy模式又很大程度上的相似:它们都有一个Context类,都是通过委托(组合)给一个具有多个派生类的多态基类实现Context的算法逻辑。两者最大的差别就是State模式中派生类持有指向Context对象的引用,并通过这个引用调用Context中的方法,但在Strategy模式中就没有这种情况。因此可以说一个State实例同样是Strategy模式的一个实例,反之却不成立。实际上State模式和Strategy模式的区别还在于它们所关注的点不尽相同:State模式主要是要适应对象对于状态改变时的不同处理策略的实现,而Strategy则主要是具体算法和实现接口的解耦(coupling),Strategy模式中并没有状态的概念(虽然很多时候有可以被看作是状态的概念),并且更加不关心状态的改变了。 多态的好处:李氏倒换使用,子类对象父类引用(Base *b = Child c;)当作父类对象使用,可以使用父类的变量操作父类变量。(子类此时的作用只是提供同名但是不同实现的方法供父类随便调用,此时父类是主角)。 子类对象父类引用什么时候能调用父类方法,什么时候能调用子类方法??? 当父类声明为虚方法(不管有没有实现),子类继承父类后实现了父类的虚方法,此时子类对象父类引用调用时调用的是子类的方法!!! 父类声明为虚方法但是实现了,子类继承父类后不实现这些虚方法,此时当子类对象父类引用调用时调用的是父类的方法!!!
Memento模式的关键就是friend class Originator;我们可以看到,Memento的接口都声明为private,而将Originator声明为Memento的友元类。我们将Originator的状态保存在Memento类中,而将Memento接口private起来,也就达到了封装的功效。
中介模式(Mediator)和代理模式(proxy):中介模式有2个对象项目交互,将两个对象指针和之间的交互逻辑交由中介类处理,而代理模式就一个本地代理类保存远程的对象的指针,将远程调用封装到本地类的方法调用。
Mediator模式是一种很有用并且很常用的模式,它通过将对象间的通信封装到一个类中,将多对多的通信转化为一对多的通信,降低了系统的复杂性。Mediator还获得系统解耦的特性,通过Mediator,各个Colleague就不必维护各自通信的对象和通信协议,降低了系统的耦合性,Mediator和各个Colleague就可以相互独立地修改了。
Command模式关键就是提供一个抽象的Command类,并将执行操作封装到Command类接口中,Command类中一般就是只是一些接口的集合,并不包含任何的数据属性
Visitor模式则提供了一种解决方案:将更新(变更)封装到一个类中(访问操作),并由待更改类提供一个接收接口,则可达到效果。
命名方法:
常量命名:下划线分隔
变量、属性命名:首字母小写的驼峰命名法,区分单复数
方法命名:首字母小写的驼峰命名法,用动词或者动宾结构
get + 非布尔属性名() is + 布尔属性名()
set + 属性名() has + 名词/形容词()
动词() 动词 + 宾语()
1 除了这两种变量外,如果某临时变量保存一段冗长代码的运算结果,以便稍后使用,这种变量应该只赋值一次。
2 如果赋值两次以上,则代表它承担了多个责任,应该分解为多个临时变量,每个变量只承担一个责任。
1、将if段落提取出来,构成一个独立函数
2、将then段落和else都提炼出来,各自构成一个独立函数
3、 编译,测试
如果某个条件极为罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回。这样的单独检查常常被称为卫语句
卫语句的核心思想是,将不满足某些条件的情况放在方法前面,并及时跳出方法,以免对后面的判断造成影响,经过这项手术的代码看起来会非常的清晰。
编码中,我们经常会遇到if/else、switch/case这样的语句,当考虑的条件越来越多,分支越来越繁时,由于惯性使然,我们常常只是在原有代码中累加分支,久而久之,代码变得臃肿不堪,圈复杂度更是严重超标。采用map去避免过多的if/else、switch/case分支,便是一种有效降低圈复杂度的方法。还可以在特定情况下使用数组.
当正确结论需要多个条件判断才会出现时,则先处理错误的分支,然后每个条件判断就立即返回。
如果switch的是0,1,2…从0开始的连续数字,则可以直接用数组保存,如果不是则用map存储。
层次 常用重构方法
合 拆 升 降
方法 1、在参数列中,将同一个对象的多个参数合并为一个对象。
2、方法的功能相同,但是参数长度不同的,使用数组/集合进行合并。
3、对复杂的条件语句进行整合,减少嵌套层次。 1、将重复的代码提取为当前类的方法。
2、将方法中有独立行为的代码块提取为方法,如for、while、if、switch代码块等。 1、将子类的相同功能的方法提升到父类或是接口中。
2、根据条件语句中的类型字符串,创建相应的子类。
3、将受到相同变化影响的方法、属性提升到一个新类中。
4、将参数或是条件语句的逻辑改为方法。
5、将复杂的表达式提升为方法。 将在个别子类中使用的父类方法放到相关的子类中。
类 1、对于一个类中使用了非常多的另一个类的属性/方法,可以考虑两个类合并。
2、通过引入其他类的实例,扩展本类的功能。 将类的多个职责拆分成不同的类,使类的职责单一。 将继承改为委托,使子类提升为单独的类,降低类之间的耦合。 1、为多个类提炼父类或是接口,其实也是“降”为子类。
2、将一个单独类继承其的委托类,成为子类,提高代码的复用。
接口隔离原则 (The Interface Segregation Principle):不应该强迫客户依赖于它们不用的方法。接口隔离原则表明客户端不应该被强迫实现一些他们不会使用的接口,应该把胖接口中的方法分组,然后用多个接口代替它,每个接口服务于一个子模块。
从执行效率看,也不应该在for循环中声明此类临时变量,因为每次循环都要创建ele对象,创建对象是要耗费内存和CPU的,而这个对象却在创建后只用了一次,浪费了系统的资源。
对于完全相同的代码,可将代码提取为新的方法,并按照代码的行为结果给新方法命名。
对于结构类似的代码,可以采取模版方法的设计模式来处理。(见示例1)
对于同一个类中不同方法的重复代码,可以将重复代码提取为新的方法。
对于不同类(子类)中的重复代码,可以将重复代码提取为新类(父类)中的方法。
- for (int i = 0; i < 1000; i++)
for (int j = 0; j < 100; j++)
for (int k = 0; k < 10; k++)
testFunction (i, j, k);
3.1 优化方案一
- for (int i = 0; i < 10; i++)
for (int j = 0; j < 100; j++)
for (int k = 0; k < 1000; k++)
testFunction (k, j, i);
该方案主要是将循环次数最少的放到外面,循环次数最多的放里面,这样可以最大程度的(注:3个不同次数的循环变量共有6种排列组合情况,此种组合为最优)减少相关循环变量的实例化次数、初始化次数、比较次数、自增次数
- int i, j, k;
- for (i = 0; i < 10; i++)
for (j = 0; j < 100; j++)
for (k = 0; k < 1000; k++)
testFunction (k, j, i);
该方案在方案一的基础上,将循环变量的实例化放到循环外,这样可以进一步减少相关循环变量的实例化次数。在嵌套For循环中,将循环次数多的循环放在内侧,循环次数少的循环放在外侧,其性能会提高;减少循环变量的实例化,其性能也会提高。
可以参考方法级别的重构方法,将类中过长的方法拆分成小的方法。
将过长的方法提升到其他的类中,然后在新类中对该方法进行重构。
对于不同类中功能类似的方法,可以通过传递不同的参数合并为一个方法,或者按照模版方法模式进行处理。
说明:修改一个类的方法时,需要许多其他的类里进行小的修改。
违反原则:开闭原则
重构方法:对受到影响的方法、属性提取到一个新类中,使它们在变化的频率和因素上保持一致。