这是设计模式系列的第一篇,系列文章目录以下:程序员
虽然不一样的设计模式解决的问题各不相同,但从一个更高的抽象层次来看,它们经过相同的手段来实现相同的目的。本文将以更抽象的视角剖析工厂模式、策略模式、模版方法模式,以及这些模式所遵循的设计原则。先用一句话总结它们的共同点:bash
它们增长了一层抽象将变化封装起来,而后对抽象编程,并利用多态应对变化。框架
应用这些设计模式的项目实战能够移步下面的连接:ide
对工厂模式来讲,变化就是构建对象的方式,举个例子:post
public class PizzaStore{
public Pizza orderPizza(String type){
Pizza pizza ;
//构建具体pizza对象
if(type.equals("cheese")){
pizza = new CheesePizza();
}else if(type.equals("bacon")){
pizza = new BaconPizza();
}
//使用pizza对象
pizza.prepare();
pizza.bake();
return pizza ;
}
}
复制代码
抽象的反义词是具体,对于工厂模式,具体就是用new
来构建对象。这样作的后果是PizzaStore
不只须要引入具体的Pizza
类,并且和构建Pizza
的细节耦合在一块儿。ui
若是PizzaStore
一生只作这两种Pizza
,上面的代码就很好,不须要重构。但若是须要新增Pizza
类型,就不得不修改orderPizza()
,向其中增长if-else
。
经过将变化封装在一层新的抽象中,实现了上层代码和变化的隔离,达到解耦的目的。对于工厂模式来讲,新建的抽象叫作工厂,它将对象的构建和对象的使用分隔开,让使用对象的代码不依赖于构建对象的细节。
解耦的好处是:“当变化发生时上层代码不须要改动”,这句话也能够表达成:“在不修改既有代码的状况下扩展功能”。这就是著名的 “开闭原则” 。
现实的问题来了,若是项目中的既有类不具有扩展性,甚至是牵一发动全身的那种类。在一个时间较紧的迭代中须要往里添加新功能,你会怎么作?是违背“对修改关闭”,仍是咬牙重构?(欢迎讨论~~)
既然目的是消除orderPizza()
中构建具体pizza
的细节,那最直接的作法是,将他们提取出来放到另外一个类中:
public class PizzaFactory{
public static Pizza createPizza(String type){
Pizza pizza ;
if(type.equals("cheese")){
pizza = new CheesePizza();
}else if(type.equals("bacon")){
pizza = new BaconPizza();
}
return pizza ;
}
}
复制代码
而后使用Pizza
的代码就变成:
public class PizzaStore{
public Pizza orderPizza(String type){
Pizza pizza = PizzaFactory.createPizza(type);
pizza.prepare();
pizza.bake();
return pizza ;
}
}
复制代码
等等,这和咱们平时将一段经常使用代码抽离出来放到Util
类中有什么区别吗?
是的,没有任何区别。从严格意义上说,这不是一个设计模式,更像是一种编程习惯。虽然只是代码搬家,但这种习惯的好处是:它隐藏了构建对象的细节,由于构建对象是常常会发生变化的,因此它还封装了变化,最后它还能够被复用,好比菜单类也须要构建 pizza 对象并获取他们的价格。
使用静态方法是这类封装经常使用的技巧,它的好处是不须要新建工厂对象就能够实现调用,但缺点是不具有扩展性(静态方法不能被重写)。
简单工厂模式中,工厂可以构建几种对象是在编译以前就定义好的,若是想要新增另外一种新对象,必须修改既有的工厂类。这不符合开闭原则。 因此简单工厂模式对于新增对象类型这个场景来讲显得不够有弹性。
有没有办法不修改既有类就新增对象类型?
工厂方法模式就能够作到,由于它采用了继承:
//抽象pizza店
public abstract class PizzaStore{
public Pizza orderPizza(String type){
Pizza pizza = createPizza(type);
pizza.prepare();
pizza.bake();
return pizza ;
}
//不一样地区的pizza店能够推出地方特点的pizza
protected abstract Pizza createPizza(String type) ;
}
//A商店提供芝士和培根两种pizza
public class PizzaStoreA extends PizzaStore{
@Override
protected Pizza createPizza(String type){
Pizza pizza ;
if(type.equals("cheese")){
pizza = new CheesePizza();
}else if(type.equals("bacon")){
pizza = new BaconPizza();
}
return pizza ;
}
}
复制代码
简单工厂模式将构建对象的细节封装在一个静态方法中(静态方法没法被继承),而工厂方法模式将其封装在一个抽象方法中,这样子类能够经过重写抽象方法新增 pizza。
如今是介绍另外一个设计原则的绝佳时机,它就是 “依赖倒置原则” :上层组件不能依赖下层组件,而且它们都不能依赖具体,而应该依赖抽象。
上面的例子中PizzaStore
是上层组件,CheesePizza
是下层组件,若是直接在PizzaStore
中构建CheesePizza
就违反了依赖倒置原则,通过工厂模式的重构,PizzaStore
依赖于Pizza
这个抽象,同时CheesePizza
也依赖于这个抽象。因此违反依赖倒置会让代码缺少弹性,不易扩展。
Android 中RecyclerView.Adapter
就运用了工厂方法模式:
public abstract static class Adapter<VH extends ViewHolder> {
//封装了各式各样ViewHolder的构建细节,延迟实现构建细节到子类中
public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
}
复制代码
若是须要构建一组对象怎么办?
抽象工厂模式用来处理这种状况,它将构建一组对象的细节封装在一个接口中:
//抽象原料工厂(原材料构建者)
public interface IngredientFactory{
void Flour createFlour() ;
void Sause createSause();
}
//原材料使用者
public class Pizza{
private Flour flour;
private Sause sause;
//使用组合持有构建者
private IngredientFactory factory;
//注入一个具体构建者
//同一种pizza,在不一样地区可能会有不一样口味
//那是由于虽然用的是同类原材料(抽象),当产地不一样味道就不一样(具体)
public Pizza(IngredientFactory factory){
this.factory = factory;
}
//使用具体工厂构建原材料(发生多态的地方)
public void prepare(){
flour = factory.createFlour();
sause = factory.createSause();
}
public void bake(){}
public void cut(){}
}
//具体工厂
public class FactoryA implements IngredientFactory{
public Flour createFlour(){
return new FlourA();
}
public Sause createSause(){
return new SauseA();
}
}
//构建pizza的时候传入具体工厂
public class PizzaStoreA extends PizzaStore{
@Override
protected Pizza createPizza(String type){
Pizza pizza ;
FactoryA factory = new FactoryA();
if(type.equals("cheese")){
pizza = new CheesePizza(factory);
}else if(type.equals("bacon")){
pizza = new BaconPizza(factory);
}
return pizza ;
}
}
复制代码
若是地区B开了一家新pizza店,只须要新建FactoryB
并在其中定义地区B原材料的构建方式,而后传入Pizza
类,整个过程不须要修改Pizza
基类。
抽象工厂模式 和 工厂方法模式 的区别在于:
对策略模式来讲,变化就是一组行为,举个例子:
public class Robot{
public void onStart(){
goWorkAt9Am();
}
public void onStop(){
goHomeAt9Pm();
}
}
复制代码
机器人天天早上9点工做。晚上9点回家。公司推出了两款新产品,一款早上8点开始工做,9点回家。另外一款早上9点工做,10点回家。
面对这样的行为变化,继承是能够解决问题的,不过你须要新建两个Robot
的子类,重载一个子类的onStart()
,重载另外一个子类的onStop()
。若是每次行为变动都经过继承来解决,那子类的数量就会愈来愈多(膨胀的子类)。更重要的是,添加增子类是在编译时新增行为, 有没有办法能够在运行时动态的修改行为?
经过将变化的行为封装在接口中,就能够实现动态修改:
//抽象行为
public interface Action{
void doOnStart();
void doOnStop();
}
public class Robot{
//使用组合持有抽象行为
private Action action;
//动态改变行为
public void setAction(Action action){
this.action = action;
}
public void onStart(){
if(action!=null){
action.doOnStart();
}
}
public void onStop(){
if(action!=null){
action.doOnStop();
}
}
}
//具体行为1
public class Action1 implements Action{
public void doOnStart(){
goWorkAt8Am();
}
public void doOnStop(){
goHomeAt9Pm();
}
}
//具体行为2
public class Action2 implements Action{
public void doOnStart(){
goWorkAt9Am();
}
public void doOnStop(){
goHomeAt10Pm();
}
}
//将具体行为注入行为使用者(运行时动态改变)
public class Company{
public static void main(String[] args){
Robot robot1 = new Robot();
robot1.setAction(new Action1());
robot1.setAction(new Action2());
}
}
复制代码
策略模式将具体的行为和行为的使用者隔离,这样的好处是,当行为发生变化时,行为的使用者不须要变更。
Android 中的各类监听器都采用了策略模式,好比View.setOnClickListener()
,但下面这个更偏向于观察者模式,他们的区别是策略模式的意图在于动态替换行为,当第二次调用setOnClickListener()
时,以前的行为被替换,而观察者模式是动态添加观察者:
public class RecyclerView{
//使用组合持有抽象滚动行为
private List<OnScrollListener> mScrollListeners;
//抽象滚动行为
public abstract static class OnScrollListener {
public void onScrollStateChanged(RecyclerView recyclerView, int newState){}
public void onScrolled(RecyclerView recyclerView, int dx, int dy){}
}
//动态修改滚动行为
public void addOnScrollListener(OnScrollListener listener) {
if (mScrollListeners == null) {
mScrollListeners = new ArrayList<>();
}
mScrollListeners.add(listener);
}
//使用滚动行为
void dispatchOnScrollStateChanged(int state) {
if (mLayout != null) {
mLayout.onScrollStateChanged(state);
}
onScrollStateChanged(state);
if (mScrollListener != null) {
mScrollListener.onScrollStateChanged(this, state);
}
if (mScrollListeners != null) {
for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
mScrollListeners.get(i).onScrollStateChanged(this, state);
}
}
}
}
复制代码
列表滚动后的行为各不相同,因此使用抽象类将其封装起来(其实和接口是同样的)。
策略模式的实战应用能够点击这里
从严格意义上讲,模版方法模式并不能套用开篇的那句话:“它们增长了一层抽象将变化封装起来,而后对抽象编程,并利用多态应对变化”。由于若是这样说,就是在强调目的是 “应对变化” 。但模版方法的目的更像是 “复用算法”,虽然它也有应对变化的成分。
对模版方法模式来讲,变化就是算法的某个步骤,举个例子:
public class View{
public void draw(Canvas canvas) {
...
// skip step 2 & 5 if possible (common case)
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we’re done...
return;
}
...
}
protected void dispatchDraw(Canvas canvas) {
}
}
复制代码
节选了View.draw()
方法中的某一片断,从注释中能够看出draw()
定义了一个绘图的算法框架,一共有七个步骤,全部步骤都被抽象成一个方法,其中的变化在于,每一个步骤对于不一样类型的View
均可能是不一样的。因此为了让不一样View
复用这套算法框架,就把它定义在了父类中,子类能够经过重写某一个步骤来定义不一样的行为。
模版方法模式一种经常使用的重构方法,它将子类的共用逻辑抽象到父类中,并将子类特有子逻辑设计成抽象方法供子类重写。
从代码层面看,模版方法的实现方式和工厂方法模式几乎同样,都是经过子类重写父类的方法。惟一的不一样是,工厂方法模式父类中的方法必须是抽象的,也就是说强制子类实现,由于子类不实现父类就没法工做。而模版方法模式父类中的方法能够是不抽象的,也就是说子类能够不实现,父类照样能工做。这种在父类中空实现的方法有一个专门的名字叫 “hook(钩子)” ,钩子的存在,可让子类有能力对算法的流程进行控制,好比ViewGroup.onInterceptTouchEvent()
。
从产出来看,模版方法模式和策略模式是一个阵营的,由于他们产出的都是一组行为(算法),而工厂模式产出的是一个对象。但它们俩对算法的控制力不一样,策略模式能够轻松的替换掉整个算法,而模版方法模式只能替换掉算法中的某个步骤。从代码层面来看,它们的实现方式也不一样,策略模式使用组合,而模版方法模式使用继承。组合比继承更具备弹性,由于它能够在运行时动态的替换行为。
这个系列会持续分享对其余设计模式的理解。
《Head First》是一本让我对设计模式理解升级的书,强烈推荐(本篇中工厂模式的例子摘自其中)。