设计模式的两大主题是系统复用和扩展
开闭原则
作为设计原则的灵魂,开闭原则的定义是:软件实体(类、模块、函数)应该对扩展开放,对修改关闭。
通俗讲就是以扩展的姿态来拥抱变化,而不是修改已有的实体,前提就需要我们在设计之初就对可能发生的变化有所考虑。废话不多先上代码:
初始需求是实现饭店往外卖菜品,卖出去一个就记录一个
饭店类:
public class restaurant
{
private ArrayList<IGoods> goodslist=new ArrayList<IGoods>();
public static void main(String args[])
{
goodslist.add(new dishes("锅包肉",20));
goodslist.add(new dishes("西兰花",20));
//显示已经卖出的菜
for(IGoods g:goodslist)
{
g.getName();
System.out.println("\n");
g.getPrice();
}
}
}
商品接口 //之所以采用接口,是因为依赖倒置原则,后面会叙述
public interface IGoods
{
public String getName();
public double getPrice();
}
public class Dishes implements IGoods
{
private String name;
private double price;
public Dishes(String _name,double _price)
{
this.name=_name;
this.price=_price;
}
@Override
public String getName()
{
System.out.println("菜名:"+name);
}
@Override
public double getPrice()
{
System.out.println("价格:"+price);
}
}
ok初始需求我们实现了,唯物主义说:唯一不变的是变,新需求是在商品中加入了酒,而且酒不只有名称、价格,还有年份。这就涉及到用什么方式来实现这个需求变更。
修改法:(不推荐)
修改后的商品接口
public interface IGoods
{
public String getName();
public double getPrice();
//新增获取年份方法
public Date getYears();
}
接口或者抽象类是规约,一旦确定不允许修改(除非重构),所以这里一修改接口,多个实体也要跟着修改,修改后代码如下:
public class Dishes implements IGoods
{
private String name;
private double price;
private Date years;
public Dishes(String _name,double _price)
{
this.name=_name;
this.price=_price;
}
@Override
public String getName()
{
System.out.println("菜名:"+name);
}
@Override
public double getPrice()
{
System.out.println("价格:"+price);
}
@Override
public Date getYears()
{
//因为菜品没有年份,但是实现了新的接口,所以这里重写方法,但是方法体里为空
}
}
修改完接口约束的类我们才能继续实现我们要实现的变更,代码如下:
public class Win implements IGoods
{
private String name;
private double price;
private Date years;
public Win(String _name,double _price,Date _years)
{
this.name=_name;
this.price=_price;
this.years=_years;
}
@Override
public String getName()
{
System.out.println("菜名:"+name);
}
@Override
public double getPrice()
{
System.out.println("价格:"+price);
}
@Override
public Date getYears()
{
System.out.println("年份:"+years);
}
}
这种需求变更的实现方式(修改式)无形中给以后的测试,维护都增添了额外的负担。而且扩散了变化的风险,所以不推荐。下面是以扩展的方式:
初始需求的代码不变,当需求发生变更时,我们设计一个酒品接口继承商品接口
public interface IWins extends IGoods
{
public Date getYears();
}
public class Win implements IWins
{
private String name;
private double price;
private Date years;
public Win(String _name,double _price,Date _years)
{
this.name=_name;
this.price=_price;
this.years=_years;
}
@Override
public String getName()
{
System.out.println("酒名:"+name);
}
@Override
public double getPrice()
{
System.out.println("价格:"+price);
}
@Override
public Date getYears()
{
System.out.println("年份:"+years);
}
}
对restaurant类稍做修改,restaurant类在这里担任了场景类的职责,也担任了初始数据(相当于持久化层)的职责,属于高层次模块,因为需求的变更必然会使高层次模块做部分改变以适应新业务。所以这部分稍做改变是不可避免的,下面上代码
public class restaurant
{
private ArrayList<IGoods> goodslist=new ArrayList<IGoods>();
public static void main(String args[])
{
goodslist.add(new dishes("锅包肉",20));
goodslist.add(new dishes("西兰花",20));
goodslist.add(new Wins("二锅头",20,"2017-4-30");
//显示已经卖出的商品
for(IGoods g:goodslist)
{
g.getName();
System.out.println("\n");
g.getPrice();
}
}
}
用扩展的方式,原先已有的模块代码没有修改,保持了历史的纯洁性,提高了系统的稳定性,有效规避了变化风险扩散。同时也可以节省测试的负担。
单一职责原则
英文简称SRP,There should never be more than one reason for a class to change(类的变化只能由唯一原因引起),由于类的职责的划分受具体情况影响,而且职责没有量化标准,所以在设计时要有单一职责这个意识,还要结合具体情况。
里氏替换原则
父类出现的地方用其子类替换不会产生任何错误。里氏替换原则为良好的继承定义了一个规范:①子类必须完全实现父类方法。
②子类可以有自己的个性。
③覆盖或实现父类方法时输入参数可以被放大(子类中的方法的前置条件必须与超类中被覆写的方法的前置条件相同或者更宽松)。
④覆盖或实现父类方法时输出结果可以被缩小。
③:
public class Father
{
public void funct(Map map)
{
System.out.println("这是父类,输入为Map");
}
}
public class Sona extends Father
{
//注意,函数名相同参数不同,是重载
@Overload
public void funct(Object ob)
{
System.out.println("子类,输入为Object");
}
}
public class Sonb extends Father
{
@Override
public void funct(Map map)
{
System.out.println("子类,输入为Map");
}
}
public class Sonc extends Father
{
@Override
public void funct(HashMap hmap)
{
System.out.println("子类,输入为HashMap");
}
}
Songa和Songb都是对的,Songc不遵循里氏替换原则,给出情形:
public class Cilent
{
public static void main(String args[])
{
Map m=new Map();
Father f=new Father();
f.funct(m);
Sona a=new Sona();
a.funct(m);
Sonb b=new Sonb();
b.funct(m);
Sonc c=new Sonc();
c.funct(m);//这里会报出异常
}
}
④同理,之所以会这样,根本原因是在赋值时,子类可以给超类赋值,而父类不能给子类赋值即 Map m=new HashMap()这个对,HashMap hm=new Map()这个错。
依赖倒置原则
依赖正置是依赖于现实依赖于具体,依赖倒置是依赖于抽象,依赖倒置原则是实现开闭原则的重要途径和手段。依赖倒置的含义是:细节依赖于抽象,抽象不应该依赖于细节;高层模块不应该依赖于低层模块,他们应该依赖于他们的抽象。下面给出解释:
饭店里不管是酒水,菜品,都可以成为商品,商品就是一个抽象的概念,我们把它当作一个抽象类,而酒水,菜品就是这个抽象类的具体表现,这样在饭店类中依赖于抽象商品接口。
public interface IGoods
{
}
public class restaurant
{
private ArrayList<IGoods> goodslist=new ArrayList<IGoods>();
}
public class dishes implements IGoods
{
}
public class wins implements IGoods
{
}
依赖的传递方法:
①设值注入
public class restaurant
{
private IGoods goods;//注意设值注入变量名前两个字母小写
public void setGoods(IGoods g)
{
goods=g;
}
}
②构造函数注入
public class restaurant
{
private IGoods goods;//注意设值注入变量名前两个字母小写
public restaurant(IGoods g)
{
goods=g;
}
}
③接口注入
依赖倒置原则通过抽象使各类或模块间彼此独立,使模块间低耦合。
接口隔离原则
类间的依赖关系应该建立在最小的接口上,接口要细化,要高内聚(提高内部处理能力,减少不必要的对外交互),一个接口只服务于一个业务逻辑或者子模块。
最少知识原则
一个对象应该对其他对象有最少的了解。最少知识原则旨在为类间解耦提出解决理念。
含义:
①只与直接朋友类通信
朋友类指出现在成员变量或者方法的输入输出参数的类。
public class A
{
public int increased(int n)
{
return n++;
}
}
public class B //正确做法
{
private int n=0;
private A a;
public void add()
{
System.out.println(a.increased(n));
}
}
public class C //错误做法
{
private int n=0;
public void add()
{
A a =new A();
System.out.println(a.increased(n));
}
}
C类中既没有A类对象作为成员变量,在C类的方法里又没有A类对象作为输入输出参数,所以C类就不知道自己与A类产生了依赖,这样是不允许的。
②类之间的依赖关系也要注意耦合性
*未经允许,不得转载*