设计模式(一):策略模式
本文将以一个小Demo及其新需求来分析使用策略模式的好处。
设计模式简述:
设计模式:
1.设计模式是人们在面对同类型软件工程设计问题所总结出的一些有用的经验。模式不是代码,而是某类问题的通用设计解决方案。
2.经典书籍《设计模式》
3.设计模式的优点和用途:和其他相关人员沟通有一定的术语基础。
4.学习设计模式最好的方式:在你的实际和以往的工程里寻找何处可以使用它。
5.设计模式的本质目的是使软件工厂在维护性、扩展性、变化性、复杂度方面O(N)。
6.OO是原则,设计模式是具体方法、工具。
在java里IO流的类设计,为什么把BufferedReader设计成:
new BufferedReader(new FileReader(“F:\test.java”));
而不是设计成:
BufferedReader extends FileReader;
然后new BufferedReader(“F:\test.java”);
因为IO流种类很多,基类、扩展子类有很多种,继承会编程n对n的关系。复杂度为n*n
设计模式有哪些?
共23种设计模式:
策略模式
观察者模式
装饰者模式
单例模式
工厂模式
命令模式
适配器模式
外观模式
模板模式
迭代器模式
组合模式
状态模式
代理模式
复合模式
桥接模式
生成器模式
责任链模式
蝇量模式
解释器模式
中介者模式
备忘录模式
原型模式
访问者模式
策略模式简述:
策略模式:
分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。
原则就是:分离变化部分,封装接口,基于接口编程各种功能。此模式让行为的变化独立于算法的使用者。
Demo描述:(OO原则解决方式)
模拟鸭子项目:
- 从项目“模拟鸭子游戏”开始
- 从OO角度设计这个项目,鸭子超类,扩展超类。
.java文件说明:
Dock.java:基类鸭子
GreenHeadDuck.java:绿头鸭
RedHeadDuck.java:红头鸭
StimulateDuck.java:模拟鸭子(main)
Dock.java:
//抽象类:鸭子
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBeahavior;
public Duck(){//子类的构造函数中可以定义行为
}
//在本抽象类中已经自己实现了
public void Quack(){
System.out.println("~~嘎嘎嘎~~");
}
//由子类去实现
public abstract void display();
//在本抽象类中已经自己实现了
public void swim(){
System.out.println("~~我会游泳~~");
}
}
GreenHeadDuck.java:
public class GreenHeadDuck extends Duck {
@Override
public void display() {
System.out.println("我和你们不一样,我是绿色的头");
}
}
RedHeadDuck.java:
public class RedHeadDuck extends Duck {
@Override
public void display() {
System.out.println("我是独一无二的,我是红头鸭。");
}
}
StimulateDuck.java:
GreenHeadDuck greenHeadDuck=new GreenHeadDuck();
RedHeadDuck redHeadDuck=new RedHeadDuck();
greenHeadDuck.display();
greenHeadDuck.Quack();
greenHeadDuck.swim();
redHeadDuck.display();
redHeadDuck.Quack();
redHeadDuck.swim();
上面的代码都是java基础。好了,我们已经实现了基本的项目需求了。模拟鸭子算是成功啦~!
项目的新需求:
1.应对新的需求,看看这个设计的可扩展性
1):添加会飞的鸭子(并不是所有的鸭子都能飞)
** OO思维里的继承方式解决方案:
如果在基类写Fly()方法,这个Fly让所有的子类都会飞了,违背逻辑。
继承问题:对类的局部改动,尤其超类的局部改动,会影响其他部分,影响会有溢出效应。
继续用OO原理来解决,覆盖:(后期工作量大)**
2):又有新需求,石头鸭子,填坑:
public class StoneDuck extends Duck{
…
}
超类挖的一个坑,每个子类都要来填,增加工作量,复杂度O(N^2).不是好的设计方式
综上,如果在基类Duck中添加fly()方法,然后让子类去继承。但是不是每个鸭子都能飞。比如北京烤鸭。。。试问,烤鸭也是鸭的子类,它继承了鸭,那么基类鸭的fly()方法,它也会有。意思是可以在烤鸭中调用fly()方法,显然这是不可能的。
所以, 我们应该解决这个问题。并不是每个子类都需要该方法。
这时候,我们应该考虑哈继承的弊端了。使用接口就可以很好的解决这个问题。(思路:继承是实现共性,减少代码的重复。接口是实现特性。)
用策略模式来解决新需求:
需要新的设计方式,应对项目的扩展性,降低复杂度:
1):分析项目变化与不变部分,提取变化部分,抽象成接口+实现;(抽象是共性,接口是特性)
2):鸭子那些功能是会根据新需求变化的?叫声、飞行
接口:
1):public interface FlyBehavior{
void fly();
}
2):public interface QuackBehavior{
void quack();
}
3):好处:新增行为简单,行为类更好的复用,组合方便。既有继承带来的复用好处,没有挖坑。
策略模式解决代码:
.java文件说明:
Dock.java:基类鸭子
GreenHeadDuck.java:绿头鸭
RedHeadDuck.java:红头鸭
StimulateDuck.java:模拟鸭子(main)
FlyBehavior.java:(接口)特有的飞行行为
QuackBehavior.java:(接口)特有的叫喊行为
BadFlyBehavior.java:飞行行为的实现类
BadQuack.java:叫喊行为的实现类
Dock.java:
//抽象类:鸭子
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBeahavior;
public Duck(){//子类的构造函数中可以定义行为
}
//在本抽象类中已经自己实现了
public void Quack(){
// System.out.println("~~嘎嘎嘎~~");
quackBeahavior.quack();
}
//由子类去实现
public abstract void display();
//在本抽象类中已经自己实现了
public void swim(){
System.out.println("~~我会游泳~~");
}
//实例化对象时可以动态的改变对象的行为(比继承灵活性强)
public void SetFlyBehavoir(FlyBehavior fb){
flyBehavior=fb;
}
//实例化对象时可以动态的改变对象的行为
public void SetQuackBehavoir(QuackBehavior qb){
quackBeahavior=qb;
}
public void Fly(){
flyBehavior.fly();
}
}
GreenHeadDuck.java:
/**
* 绿头鸭:继承自基类Duck
*/
public class GreenHeadDuck extends Duck {
public GreenHeadDuck(){
//行为轴展示具体的行为
flyBehavior=new BadFlyBehavior();
}
@Override
public void display() {
System.out.println("我和你们不一样,我是绿色的头");
}
//覆盖超类的
// public void Fly() {
// System.out.println("我不会飞!");
// }
}
RedHeadDuck.java:
/**
* 红头鸭:继承基类Duck
*/
public class RedHeadDuck extends Duck {
public RedHeadDuck(){
quackBeahavior=new BadQuack();
}
@Override
public void display() {
System.out.println("我是独一无二的,我是红头鸭。");
}
}
FlyBehavior.java:
public interface FlyBehavior {
void fly();
}
QuackBehavior.java:
public interface QuackBehavior {
void quack();
}
BadFlyBehavior.java:
public class BadFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("---我不会飞---");
}
}
BadQuack.java:
public class BadQuack implements QuackBehavior {
@Override
public void quack() {
System.out.println("我不会叫~");
}
}
StimulateDuck.java:
/**
* 主类:模拟鸭子
*/
public class StimulateDuck {
public static void main(String[] args){
//父类为Duck,屏蔽了超类的差别性
Duck greenHeadDuck=new GreenHeadDuck();
Duck redHeadDuck=new RedHeadDuck();
greenHeadDuck.display();
greenHeadDuck.Fly();
greenHeadDuck.SetQuackBehavoir(new QuackBehavior() {
@Override
public void quack() {
System.out.println("我会叫");
}
});
// greenHeadDuck.Quack();
greenHeadDuck.swim();
System.out.println();
System.out.println();
redHeadDuck.display();
redHeadDuck.Quack();
redHeadDuck.swim();
redHeadDuck.SetFlyBehavoir(new FlyBehavior() {
@Override
public void fly() {
System.out.println("我会飞");
}
});
}
}
注:以上代码都是java中的基础知识。运用设计模式中的策略模式。把变化的部分提取出来变为接口+实现。其中,main方法中,父类为Dock基类,是为了屏蔽子类的超类Dock的差别性。Dock类中的SetQuackBehavoir()方法,灵活的让实例化对象灵活的改变对象的行为。比如,绿头鸭,使用了SetQuackBehavoir()方法,定制了自己的quck()方法。因为不是每只鸭都能叫的。叫的是当前鸭的特性。
本文中的小Demo不用继承的原因是。我们可以假想。一个鸭子可以有很多子类。如果把方法全部写在基类。子类都会继承它,对于不同的子类,有些方法违反了逻辑成为了冗余。关于IO流中用的装饰者模式,而不是继承模式,不写为new BufferedReader(“F:\test.java”);
是因为IO流种类很多,基类、扩展子类有很多种,继承会编程n对n的关系。复杂度为n*n。每个IO的子类去继承超类,继承多次虽然调用的时候简写代码,但是对基类和子类的结构来说确是一种负担。所以IO流采用的是装饰者模式:new BufferedReader(new FileReader(“F:\test.java”));
(小Ddemo过于简单,就不细致分析,对比新需求前看源码即可明白策略模式的妙处)
总结:
策略模式注意点:
1.分析项目中变化部分与不变部分
2.多用组合少用继承;用行为类组合,而不是行为的继承。更有弹性。
3.设计模式没有相应的库直接使用,有些库或框架本身就用某种设计模式设计的。