本章非常重要, 里面的内容涉及到了三个设计模式, 以及接口的有点,掌握这些就是掌握了重点

1. 策略设计模式

  • 参考这篇文章:http://blog.csdn.net/chenjie19891104/article/details/6396458 讲的很清楚,策略设计模式。并且举了一个例子,很具有代表性。
  • 先简单了解一下:

  

    

    和模板方法模式的区别:

    

    文章里还有一个例子:

    

    

  

    备注:我来分解,解释一下这个例子。

    将共同的方法定义成了一个接口,在这个接口中并没有这个共同方法的实现。

    在Strategy类中,定义了一个方法execute,它的参数是拥有共同方法的接口类。

    用户Context在调用Strategy的execute方法时,在定义这个共同的方法。 这样就大大提高了灵活性。因为公共方法也是可以后定义的。而不是在创建类的时候就定义好了。

    下面把这个例子敲成代码

package net.mindview.interfaces;

//公共方法回调类
interface SameCallback{
void doTheSame();
} //定义了一个策略类
interface Strategy {
void execute(SameCallback sc);
} //定义策略实现类
class concreteStrategy1 implements Strategy {
@Override
public void execute(SameCallback sc) {
sc.doTheSame();
}
} class concreteStrategy2 implements Strategy {
@Override
public void execute(SameCallback sc) {
sc.doTheSame();
}
} class concreteStrategy3 implements Strategy {
@Override
public void execute(SameCallback sc) {
sc.doTheSame();
}
} public class Context { public static void main(String[] args) {
Strategy strategy = new concreteStrategy1();
//这个公共方法类
SameCallback sc = new SameCallback() {
@Override
public void doTheSame() {
System.out.println("do same things");
}
}; //那个策略需要用到公共方法类,调用即可,如果不用, 那就不在方法中小勇
strategy.execute(sc); strategy = new concreteStrategy2();
strategy.execute(sc);
}
}

      

      

      思考: 如果一段代码中, 总会出现很多的if...else或者case,就要考虑使用策略设计模式了

  

2. 解耦

  • 看下面这两个例子:

    
    
    案例一
    package net.mindview.interfaces.classprocessor;
    
    import java.math.BigInteger;
    import java.util.Arrays; /**
    * 处理器
    */
    class Processor {
    public String name(){
    return getClass().getName();
    };
    //处理
    Object process(Object input) {
    return input;
    }
    } /**
    * 大写处理器
    */
    class UpCase extends Processor {
    @Override
    public
    Object process(Object input) {
    return ((String)input).toUpperCase();
    }
    } /**
    * 小写处理器
    */
    class DownCase extends Processor {
    @Override
    public
    Object process(Object input) {
    return ((String)input).toLowerCase();
    }
    } /**
    * 分割数组处理器
    */
    class Splitter extends Processor {
    @Override
    public
    Object process(Object input) {
    return Arrays.toString(((String)input).toString().split(" "));
    }
    } public class Apply {
    public static void process(Processor p, Object s){
    System.out.println("Using Processor "+ p.name());
    System.out.println(p.process(s));
    } public static String s = "Disagreement with beliefs is by definition incorrect"; /**
    * 向本例这样, 创建一个能够根据所传递的参数对象的不同而具有不同行为的方法,
    * 被称为策略设计模式
    * @param args
    */
    public static void main(String[] args) {
    BigInteger b = new BigInteger(""); process(new UpCase(), s);
    process(new DownCase(), s);
    process(new Splitter(), s);
    } /**
    * 这个类使用到了一个设计模式: 叫做策略设计模式
    * 所谓 的策略设计模式指的是: 能够根据传递参数的不同而具有不同的行为的方法, 被称为策略设计模式
    * 这类方法包含了所有执行的算法中固定不变的部分(这里是基类Processor,Object),而策略就是实际调用该方法时传递的实际参数,
    * 这部分参数是变化的,因此被称之为是"策略"部分.
    * 在main中看到了三种不同类型的策略应用到了String类型s对象上。
    */
    }

    在这段代码中,定义了一个处理器,这个处理器有两个方法name()和process(). 然后定义了三个类, UpCase, DownCase,Spltter继承自这个类. 当我们在Apply类中定义process(Process process, Object o)方法, 方法传递的参数是父类Process. 这样不仅可以处理父类,还可以处理子类. 在main方法中就有体现. 这就是策略设计模式的思想。但是,他还有局限性。如果在main中想调用Apply.process()方法, 则必须传递Process类或者子类,不能传递其他类。 下面这个案例滤波器就说明了这一点:

    
    
    案例二
    
    package net.mindview.interfaces.filters;
    
    //波形
    public class Waveform {
    //计数器,第几波
    private static long counter;
    private final long id = counter ++; //当前第几波
    @Override
    public String toString() {
    return "Waveform " + id;
    }
    } package net.mindview.interfaces.filters; //滤波器
    public class Filter {
    public String name(){
    return getClass().getSimpleName();
    }
    //加工
    public Waveform process(Waveform input){
    return input;
    }
    } package net.mindview.interfaces.filters;
    /**
    * 低频电磁波
    */
    public class LowPass extends Filter{
    //切波的波长
    double cutoff;
    public LowPass(double cutoff){
    this.cutoff = cutoff;
    }
    //加工
    @Override
    public Waveform process(Waveform input) {
    return input;
    }
    } package net.mindview.interfaces.filters;
    /**
    * 高频电磁波
    */
    public class HighPass extends Filter {
    //切波
    double cutoff;
    public HighPass(double cutoff){
    this.cutoff = cutoff;
    }
    //加工
    @Override
    public Waveform process(Waveform input) {
    return input;
    }
    } package net.mindview.interfaces.filters;
    /**
    * 段波电磁波
    */
    class BandPass extends Filter {
    //其实切波点, 最高且波点
    double cutoff ,highCutoff;
    public BandPass(double cutoff, double highCutoff){
    this.cutoff = cutoff;
    this.highCutoff = highCutoff;
    }
    //加工
    @Override
    public Waveform process(Waveform input) {
    return input;
    }
    }

    在上面这个类中,我们定义了一个Filter滤波器。这里面有何Process中同名的两个方法。这个时候,我想复用案例一的Apply类中的process()方法,可不可以呢?不可以。因为Filter不是Process的子类。虽然, process中的方法体调用完全适用于继承Filter类的子类。为什么不可以呢?Apply就是一个应用类。应用类要处理一些列的类。他可以堆字符串进行处理, 应该也可以对波进行处理呀。现在不可以,因为Process类是一个实体类,他和Apply类中的process方法紧密耦合。如果能够实现解耦,那么,就可以将Apply类更加泛化。这就是将Process作为接口来处理

    案例三

    package net.mindview.interfaces.classprocessor2; import java.math.BigInteger;
    import java.util.Arrays; /**
    * 处理器
    */
    interface Processor {
    public String name();
    //处理
    Object process(Object input);
    } /**
    * 处理器有一部分是所有处理器都是一样的.有一部分是不同的.
    * 所以定义一个抽象类, 将共有方法定义为实体方法, 将各自实现的部分定义为抽象的
    */
    abstract class StringProcess implements Processor {
    @Override
    public String name() {
    return getClass().getSimpleName();
    }
    public abstract Object process(Object input);
    }
    /**
    * 大写处理器
    */
    class UpCase extends StringProcess {
    @Override
    public
    Object process(Object input) {
    return ((String)input).toUpperCase();
    }
    } /**
    * 小写处理器
    */
    class DownCase extends StringProcess {
    @Override
    public
    Object process(Object input) {
    return ((String)input).toLowerCase();
    }
    } /**
    * 分割数组处理器
    */
    class Splitter extends StringProcess {
    @Override
    public
    Object process(Object input) {
    return Arrays.toString(((String)input).toString().split(" "));
    }
    } public class Apply {
    public static void process(Processor p, Object s){
    System.out.println("Using Processor "+ p.name());
    System.out.println(p.process(s));
    } public static String s = "Disagreement with beliefs is by definition incorrect"; /**
    * 向本例这样, 创建一个能够根据所传递的参数对象的不同而具有不同行为的方法,
    * 被称为策略设计模式
    * @param args
    */
    public static void main(String[] args) {
    BigInteger b = new BigInteger(""); process(new UpCase(), s);
    process(new DownCase(), s);
    process(new Splitter(), s);
    } /**
    * 这个类使用到了一个设计模式: 叫做策略设计模式
    * 所谓 的策略设计模式指的是: 能够根据传递参数的不同而具有不同的行为的方法, 被称为策略设计模式
    * 这类方法包含了所有执行的算法中固定不变的部分(这里是基类Processor,Object),而策略就是实际调用该方法时传递的实际参数,
    * 这部分参数是变化的,因此被称之为是"策略"部分.
    * 在main中看到了三种不同类型的策略应用到了String类型s对象上。
    */
    }

    在这个类里面,将Process定义为了接口。并且添加了StringProcess 字符串处理器类,提供了字符串处理的公用方法。这时如何修改案例二,才能让Apply成为一个公用类呢?其中一个方法是让Filter实现Process接口。但为题又来了,Filter中的process的输入输出参数都是Waveform类型的对象。而Process中process的输入输出都是Object。这时我们使用一个适配器来解决这个问题

package net.mindview.interfaces.filters2;

import net.mindview.interfaces.classprocessor2.Processor;

/**
* 由于Filter中的process的输入输出参数都是Waveform,而Processor中process的输入
* 输出参数都是Object. 因此,写一个适配器,让Filter可以适配Processor类型的接口
*
*/ public class FilterAdapter implements Processor{
Filter filter;
public FilterAdapter(Filter filter){
this.filter = filter;
} @Override
public String name() {
return filter.name();
} @Override
public Object process(Object input) {
return filter.process((Waveform)input);
}
}

  接下来看看Apply类如何处理的?

public class Apply {
public static void process(Processor p, Object s){
System.out.println("Using Processor "+ p.name());
System.out.println(p.process(s));
} public static String s = "Disagreement with beliefs is by definition incorrect"; /**
* 向本例这样, 创建一个能够根据所传递的参数对象的不同而具有不同行为的方法,
* 被称为策略设计模式
* @param args
*/
public static void main(String[] args) {
System.out.println("======处理字符串===========");
process(new UpCase(), s);
process(new DownCase(), s);
process(new Splitter(), s); System.out.println("======过滤波===========");
process(new FilterAdapter(new LowPass(1.0)), new Waveform());
process(new FilterAdapter(new HighPass(100.0)), new Waveform());
process(new FilterAdapter(new BandPass(1.0,10.0)), new Waveform());
} }

  上面这个例子就示范了使用接口的好处,让类和方法解耦。同时还涉及到一个设计模式,叫适配器设计模式。适配器设计模式有三种, 确切的说这是对象适配器模式.

3. 适配器设计模式

  参考文章:http://blog.csdn.net/zxt0601/article/details/52848004

  定义:适配器模式将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)。

  

  属于结构型模式

    主要分为三类:类适配器模式、对象的适配器模式、接口的适配器模式。

  本文做以下约定

    • 需要被适配的类,对象,接口被定义为源(目前已有的),简称为src(source).
    • 最终需要输出的(我们需要的),简称dst(destination,也叫target)
    • 适配器Adapter

    一句话描述适配器模式:src->adapter->dist,即src以某种形式(类,对象,接口)给到适配器,适配器最终输出dist。

  使用 场景

    • 想要使用一个已经存在的类,但如果它的方法不满足需求时;

    • 两个类的职责相同或相似,但是具有不同的接口时要使用它;

    • 应该在双方都不太容易修改的时候再使用适配器模式适配,而不是一有不同时就使用它

   第一类: 类适配器模式

    一句话描述使用方法:Adapter类,通过继承src类,实现dist接口,完成src -> dist的适配.

    以充电器为例:充电器本身就是一个适配器。输入的时220v电压, 输出的是5v电压。

    首先, 有一个src类,本身是输出220v电压  

package net.mindview.interfaces.adaptor;

/**
* 这是目前有的电压 200v
* @author samsung
*
*/
public class Voltage220 {
public int output(){
int i = ;
System.out.println("这是220v电压");
return i;
}
}

    然后, 有一个目标使用电压是5v

package net.mindview.interfaces.adaptor;

/**
* 我们需要使用的目标电压是5v
* 这是一个5v电压的接口
* @author samsung
*
*/
public interface Voltage5 {
public int output();
}

    定义一个适配器, 将220v电压经过处理后转换为5v电压供用户使用

package net.mindview.interfaces.adaptor;

/**
* 适配器,将220v电压输出为5v电压
* 适配器 继承src类, 实现dist类,实现从src->dist的转换
*/
public class VoltageAdapter extends Voltage220 implements Voltage5{ @Override
public int output() {
int src = super.output();
System.out.println("适配器工作开始适配电压");
int dist = src/;
System.out.println("适配完成后输出电压:" + dist);
return dist;
}
}

   有一款手机需要充电,充电电压是5v

package net.mindview.interfaces.adaptor;

public class Mobile {

    public void chargeing(Voltage5 v5){
if(v5.output() == ){
System.out.println("正常充电");
} else {
System.out.println("秒变note7");
}
} }

使用手机的时候,需要将家里的220v电压转换为5v输出.调用适配器

public static void main(String[] args) {
Mobile m = new Mobile();
m.chargeing(new VoltageAdapter());
}

输出:

===============类适配器==============
我是220V
适配器工作开始适配电压
适配完成后输出电压:
电压刚刚好5V,开始充电

  

小结:

Java这种单继承的机制,所有需要继承的我个人都不太喜欢。 
所以类适配器需要继承src类这一点算是一个缺点, 
因为这要求dst必须是接口,有一定局限性; 
且src类的方法在Adapter中都会暴露出来,也增加了使用的成本。

但同样由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了。

    第二类: 对象适配器模式

    基本思路和类适配器一样, 只是将adapter类做了修改, 这次不继承src类,而是持有src类实例对象,以解决兼容性问题。

    一句话总结使用方法:持有src类,实现dist类接口, 实现src -> dist的适配  

    (根据"合成复用规则", 在系统中尽量使用组合来代替继承)

package net.mindview.interfaces.adaptor;
/**
* 持有src类,实现dist类接口, 实现src -> dist的适配
* @author samsung
*
*/
public class VoltageAdapter2 implements Voltage5{
Voltage220 voltage220; public VoltageAdapter2(Voltage220 voltage220){
this.voltage220 = voltage220;
} @Override
public int output() {
System.out.println("得到220v电压");
int src = voltage220.output();
System.out.println("经过处理,输出5v电压");
int dist = src/;
System.out.println("最终使用的电压是"+dist+"v");
return dist;
}
}

测试结果:

得到220v电压
这是220v电压
经过处理,输出5v电压
最终使用的电压是5v
正常充电

  

    

小结:

对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。 
根据合成复用原则,组合大于继承, 
所以它解决了类适配器必须继承src的局限性问题,也不再强求dst必须是接口。 
同样的它使用成本更低,更灵活。

  第三类 接口适配器模式

    

也有文献称之为认适配器模式(Default Adapter Pattern)或缺省适配器模式。 
定义: 
  当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况。

我们直接进入大家最喜爱的源码撑腰环节:

源码撑腰环节:

Android中的属性动画ValueAnimator类可以通过addListener(AnimatorListener listener)方法添加监听器, 
那么常规写法如下:

 ValueAnimator valueAnimator = ValueAnimator.ofInt(,);
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) { } @Override
public void onAnimationEnd(Animator animation) { } @Override
public void onAnimationCancel(Animator animation) { } @Override
public void onAnimationRepeat(Animator animation) { }
});
valueAnimator.start();

有时候我们不想实现Animator.AnimatorListener接口的全部方法,我们只想监听onAnimationStart,我们会如下写:

 ValueAnimator valueAnimator = ValueAnimator.ofInt(,);
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
//xxxx具体实现
}
});
valueAnimator.start();

显然,这个AnimatorListenerAdapter类,就是一个接口适配器。 
查看该Adapter类源码:

public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener,
Animator.AnimatorPauseListener {
@Override
public void onAnimationCancel(Animator animation) {
} @Override
public void onAnimationEnd(Animator animation) {
} @Override
public void onAnimationRepeat(Animator animation) {
} @Override
public void onAnimationStart(Animator animation) {
} @Override
public void onAnimationPause(Animator animation) {
} @Override
public void onAnimationResume(Animator animation) {
}
}

可见,它空实现了Animator.AnimatorListener类(src)的所有方法. 
对应的src类:

 public static interface AnimatorListener {
void onAnimationStart(Animator animation); void onAnimationEnd(Animator animation); void onAnimationCancel(Animator animation); void onAnimationRepeat(Animator animation);
}

类图:


我们程序里的匿名内部类就是Listener1 2 这种具体实现类。

 new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
//xxxx具体实现
}
}

接口适配器模式很好理解,令我们的程序更加简洁明了。

总结

我个人理解,三种命名方式,是根据 src是以怎样的形式给到Adapter(在Adapter里的形式)来命名的。 
类适配器,以类给到,在Adapter里,就是将src当做类,继承, 
对象适配器,以对象给到,在Adapter里,将src作为一个对象,持有。 
接口适配器,以接口给到,在Adapter里,将src作为一个接口,实现。

Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作。 
但是在实际开发中,实现起来不拘泥于本文介绍的三种经典形式, 
例如Android中ListView、GridView的适配器Adapter,就不是以上三种经典形式之一, 
我个人理解其属于对象适配器模式,一般日常使用中,我们都是在Adapter里持有datas,然后通过getView()/onCreateViewHolder()方法向ListView/RecyclerView提供View/ViewHolder。

Client是Lv Gv Rv ,它们是显示View的类。 
所以dst(Target)是View。 
一般来说我们有的src是数据datas, 
即,我们希望:datas(src)->Adapter->View(dst)->Rv(Client)。

4. 适配接口

  适配接口这一节,主要是再次强调了接口的好处. 可以使用策略设计模式, 在方法实际参数中调用接口, 用户使用的时候就可以根据实际情况传递实现了该接口的实现类. 灵活性更高. 同时, 课本中举了一个例子,Scanner的read方法中调用的就是readable接口。如果我想使用Scanner类(这时java类库中的类),有两种方式。

  • 方式一:新类实现readable接口即可
  • 方式二:如果类已定义好, 不方便修改, 可以使用适配器模式,让已写好的类适配Scanner类。

    针对这两个方法,我们来看案例:

    案例一(对应的方式一) 

package net.mindview.interfaces;

import java.io.IOException;
import java.nio.CharBuffer;
import java.util.Random;
import java.util.Scanner; //随机单次
public class RandomWords implements Readable{ //首先生成一个全局的随机数.
//特殊说明:创建一个Random对象的时候可以给定任意一个合法的种子数,种子数只是随机算法的起源数字,和生成的随机数的区
//间没有任何关系。其中47就是种子,那么为什么是作者要选择47作为种子呢? 原因如下:
//由47做种后,产生的随机数更加体现了随机性。它没有什么具体的意义,只要理解随机数如果有一个种子,
//那么会出现比较随机的随机数,而当种子是47的时候,随机率是最大的。
private static Random rand = new Random();
//定义首字母,字母的范围已固定,不会改变,故定义为static final
private static final char[] captials = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
//小写字母
private static final char[] lowers = "abcdefghijklmnopqrstuvwxyz".toCharArray();
//元音字母
private static final char[] vowels = "aeiou".toCharArray(); private int count; //计数,一共生成几个随机单词
public RandomWords(int count){
this.count = count;
} @Override
public int read(CharBuffer cb) throws IOException {
if(count-- == ) {
return -;
}
//生成一个大写字母
cb.append(captials[rand.nextInt(captials.length)]);
//生成一个原因一个辅音
for(int i=; i<; i++){
cb.append(vowels[rand.nextInt(vowels.length)]);
cb.append(lowers[rand.nextInt(lowers.length)]);
} cb.append(" ");
return ;//返回字符数
} public static void main(String[] args) {
Scanner s = new Scanner(new RandomWords());
while(s.hasNext()){
System.out.println(s.next());
}
}
}

  案例二(对应方式二)

  现在有一个写好的了,随机生成浮点数

package net.mindview.interfaces;

import java.util.Random;

//随机生成浮点数
public class RandomDoubles {
private static Random rand = new Random();
public double next(){
return rand.nextDouble();
} public static void main(String[] args) {
RandomDoubles rd = new RandomDoubles();
for(int i=; i< ;i++){
System.out.println(rd.next() + " ");
} }
}

  下面我们使用适配器设计模式写一个类来进行适配。适配器模式有三种种, 这里是用的是类适配器模式, 还可以使用对象适配器模式

package net.mindview.interfaces;

import java.io.IOException;
import java.nio.CharBuffer;
import java.util.Scanner; public class AdapterRandomDoubles extends RandomDoubles implements Readable{
private int count;
public AdapterRandomDoubles(int count){
this.count = count;
}
@Override
public int read(CharBuffer cb) throws IOException {
if(count-- == ){
return -;
}
String result = Double.toString(next()) + " ";
cb.append(result);
return result.length();
} public static void main(String[] args) {
Scanner s = new Scanner(new AdapterRandomDoubles());
while(s.hasNextDouble()){
System.out.println(s.nextDouble()+" ");
} } }

  下面使用的是对象适配器模式, 和类适配器模式基本相似。

package net.mindview.interfaces;

import java.io.IOException;
import java.nio.CharBuffer;
import java.util.Scanner; public class AdapterRandomDoubles implements Readable{
private RandomDoubles rd;
private int count;
public AdapterRandomDoubles(RandomDoubles rd, int count){
this.rd = rd;
this.count = count;
}
@Override
public int read(CharBuffer cb) throws IOException {
if(count-- == ){
return -;
}
String result = Double.toString(rd.next()) + " ";
cb.append(result);
return result.length();
} public static void main(String[] args) {
Scanner s = new Scanner(new AdapterRandomDoubles(new RandomDoubles(), ));
while(s.hasNextDouble()){
System.out.println(s.nextDouble()+" ");
}
} }

总结: 使用适配器模式, 我们可以在任何现有类之上添加类的接口,所以这意味着让方法接收接口类型,是一种让任何类都可以对该方法进行适配的方式。这就是使用接口而不使用类的强大之处。

 4.本章设计的最后一个设计模式: 工厂设计模式 

  参考文章: https://wenku.baidu.com/view/8293cb68302b3169a45177232f60ddccda38e62c.html###

在面向对象编程中, 最通常的方法是一个new操作符产生一个对象实例,new操作符就是用来构造对象实例的。但是在一些情况下, new操作符直接生成对象会带来一些问题。举例来说, 许多类型对象的创造需要一系列的步骤: 你可能需要计算或取得对象的初始设置; 选择生成哪个子对象实例; 或在生成你需要的对象之前必须先生成一些辅助功能的对象。 在这些情况,新对象的建立就是一个 “过程”,不仅是一个操作,像一部大机器中的一个齿轮传动。

  模式的问题:你如何能轻松方便地构造对象实例,而不必关心构造对象实例的细节和复杂过程呢?

  解决方案:建立一个工厂来创建对象

实现:

一、引言
    1)还没有工厂时代:假如还没有工业革命,如果一个客户要一款宝马车,一般的做法是客户去创建一款宝马车,然后拿来用。
    2)简单工厂模式:后来出现工业革命。用户不用去创建宝马车。因为客户有一个工厂来帮他创建宝马.想要什么车,这个工厂就可以建。比如想要320i系列车。工厂就创建这个系列的车。即工厂可以创建产品。
    3)工厂方法模式时代:为了满足客户,宝马车系列越来越多,如320i,523i,30li等系列一个工厂无法创建所有的宝马系列。于是由单独分出来多个具体的工厂。每个具体工厂创建一种系列。即具体工厂类只能创建一个具体产品。但是宝马工厂还是个抽象。你需要指定某个具体的工厂才能生产车出来。

4)抽象工厂模式时代:随着客户的要求越来越高,宝马车必须配置空调。于是这个工厂开始生产宝马车和需要的空调。

最终是客户只要对宝马的销售员说:我要523i空调车,销售员就直接给他523i空调车了。而不用自己去创建523i空调车宝马车.

这就是工厂模式。

二、分类 
        工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。 
工厂模式可以分为三类:

1)简单工厂模式(Simple Factory) 
2)工厂方法模式(Factory Method) 
3)抽象工厂模式(Abstract
Factory)

这三种模式从上到下逐步抽象,并且更具一般性。 
        GOF在《设计模式》一书中将工厂模式分为两类:工厂方法模式(Factory Method)与抽象工厂模式(Abstract Factory)。

将简单工厂模式(Simple Factory)看为工厂方法模式的一种特例,两者归为一类。

三、区别 


工厂方法模式:

    • 一个抽象产品类,可以派生出多个具体产品类。
    • 一个抽象工厂类,可以派生出多个具体工厂类。
    • 每个具体工厂类只能创建一个具体产品类的实例。

抽象工厂模式:

    • 多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
    • 一个抽象工厂类,可以派生出多个具体工厂类。
    • 每个具体工厂类可以创建多个具体产品类的实例。

  区别:
    工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。   
    工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。
    两者皆可。

四、简单工厂模式 


建立一个工厂(一个函数或一个类方法)来制造新的对象。
分布说明引子:从无到有。客户自己创建宝马车,然后拿来用。

package net.mindview.interfaces.factory;

class BMW320 {
BMW320(){ } public void make(){
System.out.println("制造-->BMW320");
}
} class BMW523 {
BMW523(){} public void make(){
System.out.println("制造-->BMW523");
}
} public class Customer {
public static void main(String[] args) {
/**
* 我现在需要一辆车BMW320
* 在没有工厂的时候, 我学要自己制造这辆车
*/
BMW320 bmw320 = new BMW320();
bmw320.make(); /**
* 我现在需要一辆车BMW523
* 在没有工厂的时候, 我学要自己制造这辆车
*/
BMW523 bmw523 = new BMW523();
bmw523.make(); } }

客户需要知道怎么去创建一款车,客户和车就紧密耦合在一起了

为了降低耦合,就出现了工厂类,

把创建宝马的操作细节都放到了工厂里面去,客户直接使用工厂的创建工厂方法,传入想要的宝马车型号就行了,而不必去知道创建的细节.这就是工业革命了:简单工厂模式

即我们建立一个工厂类方法来制造新的对象。如图:

  

package net.mindview.interfaces.factory.simplefactory;

//产品类
abstract class BMW{
} class BMW320 extends BMW{
public BMW320(){
System.out.println("制造-->BMW 320");
}
} class BMW530 extends BMW{
public BMW530(){
System.out.println("制造-->BMW530");
}
} //工厂类
class Factory {
public static final int TYPE_BMW_320 = ;
public static final int TYPE_BMW_530 = ; public static BMW make(int type){
switch(type){
case :
return new BMW320();
case :
return new BMW530();
default:
return null;
}
}
} public class Customer {
public static void main(String[] args) {
//我想要一辆车
BMW b320 = Factory.make(Factory.TYPE_BMW_320);
BMW b530 = Factory.make(Factory.TYPE_BMW_530);
} }

简单工厂模式又称静态工厂方法模式。重命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。

先来看看它的组成: 
         1) 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,用来创建产品
         2) 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。         
         3) 具体产品角色:工厂类所创建的对象就是此角色的实例。在Java中由一个具体类实现。 
        
         下面我们从开闭原则(对扩展开放;对修改封闭)上来分析下简单工厂模式。当客户不再满足现有的车型号的时候,想要一种速度快的新型车,只要这种车符合抽象产品制定的合同,那么只要通知工厂类知道就可以被客户使用了。所以对产品部分来说,它是符合开闭原则的;但是工厂部分好像不太理想,因为每增加一种新型车,都要在工厂类中增加相应的创建业务逻辑(createBMW(int type)方法需要新增case),这显然是违背开闭原则的。可想而知对于新产品的加入,工厂类是很被动的。对于这样的工厂类,我们称它为全能类或者上帝类。

我们举的例子是最简单的情况,而在实际应用中,很可能产品是一个多层次的树状结构。由于简单工厂模式中只有一个工厂类来对应这些产品,所以这可能会把我们的上帝累坏了,也累坏了我们这些程序员。
        于是工厂方法模式作为救世主出现了。 工厂类定义成了接口,而每新增的车种类型,就增加该车种类型对应工厂类的实现,这样工厂的设计就可以扩展了,而不必去修改原来的代码。

五、工厂方法模式 


        工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。

工厂方法模式组成: 
       1)抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。 
       2)具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。 
       3)抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。 
       4)具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。

工厂方法模式使用继承自抽象工厂角色的多个子类来代替简单工厂模式中的“上帝类”。正如上面所说,这样便分担了对象承受的压力;而且这样使得结构变得灵活 起来——当有新的产品产生时,只要按照抽象产品角色、抽象工厂角色提供的合同来生成,那么就可以被客户使用,而不必去修改任何已有 的代码。可以看出工厂角色的结构也是符合开闭原则的!

代码如下:

package net.mindview.interfaces.factory.factoryMethod;
//产品
interface BMW{ } class BMW320 implements BMW{
public BMW320() {
System.out.println("制造-->BMW 320");
}
} class BMW530 implements BMW{
public BMW530() {
System.out.println("制造-->BMW 530");
}
} class BMW680 implements BMW{
public BMW680() {
System.out.println("制造-->BMW 680");
}
} //工厂
interface BMWFactory{
public BMW make();
} class BMW320Factory implements BMWFactory{
@Override
public BMW make() {
return new BMW320();
}
} class BMW530Factory implements BMWFactory{
@Override
public BMW make() {
return new BMW530();
}
} class BMW680Factory implements BMWFactory{
@Override
public BMW make() {
return new BMW680();
}
} //客户类
public class Customer {
public static BMW serviceCustomer(BMWFactory factory){
return factory.make();
}
public static void main(String[] args) {
BMW bmw320 = serviceCustomer(new BMW320Factory());
BMW bmw530 = serviceCustomer(new BMW530Factory());
BMW bmw680 = serviceCustomer(new BMW680Factory()); } }

工厂方法模式仿佛已经很完美的对对象的创建进行了包装,使得客户程序中仅仅处理抽象产品角色提供的接口,但使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象,这不是我们所希望的。

本文继续介绍23种设计模式系列之抽象工厂模式。

前面已经介绍过简单工厂模式和工厂方法模式,这里继续介绍第三种工厂模式-抽象工厂模式,还是以汽车的制造为例。

例子背景:

随着客户的要求越来越高,宝马车需要不同配置的空调和发动机等配件。于是这个工厂开始生产空调和发动机,用来组装汽车。这时候工厂有两个系列的产品:空调和发动机。宝马320系列配置A型号空调和A型号发动机,宝马230系列配置B型号空调和B型号发动机。

概念:

抽象工厂模式是工厂方法模式的升级版本,他用来创建一组相关或者相互依赖的对象。比如宝马320系列使用空调型号A和发动机型号A,而宝马230系列使用空调型号B和发动机型号B,那么使用抽象工厂模式,在为320系列生产相关配件时,就无需制定配件的型号,它会自动根据车型生产对应的配件型号A。

针对百度百科上对于抽象工厂模式的简介,结合本例如下:

当每个抽象产品都有多于一个的具体子类的时候(空调有型号A和B两种,发动机也有型号A和B两种),工厂角色怎么知道实例化哪一个子类呢?比如每个抽象产品角色都有两个具体产品(产品空调有两个具体产品空调A和空调B)。抽象工厂模式提供两个具体工厂角色(宝马320系列工厂和宝马230系列工厂),分别对应于这两个具体产品角色,每一个具体工厂角色只负责某一个产品角色的实例化。每一个具体工厂类只负责创建抽象产品的某一个具体子类的实例。

抽象工厂模式代码

package net.mindview.interfaces.factory.abstractFactory;

//产品
interface Engine{ } class Engine320 implements Engine{
public Engine320(){
System.out.println("我是Engine320发动机");
}
} class Engine530 implements Engine{
public Engine530(){
System.out.println("我是Engine530发动机");
}
} interface AirCondition {} class AirCondition320 implements AirCondition {
public AirCondition320(){
System.out.println("我是AirCondition320空调");
}
} class AirCondition530 implements AirCondition {
public AirCondition530(){
System.out.println("我是AirCondition530空调");
}
} //汽车
interface BMW{ } class BMW320 implements BMW{
public BMW320(Engine e, AirCondition ma) {
System.out.println("制造-->BMW 320");
}
} class BMW530 implements BMW{
public BMW530(Engine e, AirCondition ma) {
System.out.println("制造-->BMW 530");
}
} //工厂
interface BMWFactory {
public Engine makeEngine();
public AirCondition makeAirCondition();
public BMW makeBMW();
} class BMWFactory320 implements BMWFactory{ @Override
public Engine makeEngine() {
return new Engine320();
} @Override
public AirCondition makeAirCondition() {
return new AirCondition320();
} @Override
public BMW makeBMW() {
return new BMW320(makeEngine(), makeAirCondition());
}
} class BMWFactory530 implements BMWFactory{ @Override
public Engine makeEngine() {
return new Engine530();
} @Override
public AirCondition makeAirCondition() {
return new AirCondition530();
} @Override
public BMW makeBMW() {
return new BMW530(makeEngine(), makeAirCondition()); }
} public class Customer {
public static BMW serviceCustomer(BMWFactory factory){
return factory.makeBMW();
}
public static void main(String[] args) {
BMW b320 = serviceCustomer(new BMWFactory320());
BMW b530 = serviceCustomer(new BMWFactory530());
}
}

  运行结果:

我是Engine320发动机
我是AirCondition320空调
制造-->BMW 320 我是Engine530发动机
我是AirCondition530空调
制造-->BMW

  关于抽象工厂模式与工厂方法模式的区别,这里就不说了,感觉多看几遍例子就能理解,还有很多提到的产品族、等级结构等概念,说了反而更难理解。

java编程思想第四版第九章总结的更多相关文章

  1. java编程思想第四版第九章习题

    第三题 package net.mindview.interfaces; abstract class Base{ public Base(){ print(); } abstract void pr ...

  2. Java编程思想第四版第二章练习题答案

    练习1:创建一个类,它包含一个int域和一个char域,它们都没有被初始化.将他们的值打印出来,以验证Java执行了默认初始化 public class JavaThinking { private ...

  3. java编程思想第四版第二章要点总结

    1. 基本类型 基本类型 二进制位数 包装器类 boolean - Boolean byte 8 Byte char 16 Character short 16 Short int 32 Intege ...

  4. 《Java编程思想第四版》附录 B 对比 C++和 Java

    <Java编程思想第四版完整中文高清版.pdf>-笔记 附录 B 对比 C++和 Java “作为一名 C++程序员,我们早已掌握了面向对象程序设计的基本概念,而且 Java 的语法无疑是 ...

  5. java编程思想第四版中net.mindview.util包下载,及源码简单导入使用

    在java编程思想第四版中需要使用net.mindview.util包,大家可以直接到http://www.mindviewinc.com/TIJ4/CodeInstructions.html 去下载 ...

  6. Java编程思想第四版勘误

    坊间传说这本书翻译得很烂,我倒觉得还好.虽然看原文更准确,但是如果在具备一定编程思维和基础.能够看出来疑问的情况下,还是看中文更快一些,而且这本书本身也不适合初学者看.当然,错误和不通顺还是有的,而且 ...

  7. Java编程思想第四版完整中文高清版.pdf

    Java编程思想第四版完整中文高清版.pdf 链接: https://pan.baidu.com/s/1vV5BHF3L-bnaG6WGurdJ_A 提取码: vigy 复制这段内容后打开百度网盘手机 ...

  8. 《Java编程思想第四版完整中文高清版.pdf》-笔记

    D.2.1 安插自己的测试代码 插入下述“显式”计时代码,对程序进行评测: long start = System.currentTimeMillis(); // 要计时的运算代码放在这儿 long ...

  9. 《Java编程思想第四版》附录 C Java 编程规则

    附录 C Java 编程规则 本附录包含了大量有用的建议,帮助大家进行低级程序设计,并提供了代码编写的一般性指导: (1) 类名首字母应该大写.字段.方法以及对象(句柄)的首字母应小写.对于所有标识符 ...

随机推荐

  1. 保存文件名至txt文件中,不含后缀

    准备深度学习的训练数据时,可能会用到将图片文件名保存到txt文件中,所以用python实现了该功能.输入参数只设了两个,图片存放路径,和输出的txt文件名. 代码里写死了只识别.jpg格式,并不进行目 ...

  2. python学习:字典排序

    按字典值排序   按照字典value排序,类似sort -k 命令   import operator x= {1:2,3:4,4:3,2:1,0:0} sorted_x = sorted(x.ite ...

  3. [翻译] 编写高性能 .NET 代码--第二章 GC -- 避免使用终结器,避免大对象,避免复制缓冲区

    避免使用终结器 如果没有必要,是不需要实现一个终结器(Finalizer).终结器的代码主要是让GC回收非托管资源用.它会在GC完成标记对象为可回收后,放入一个终结器队列里,在由另外一个线程执行队列里 ...

  4. 韩信点兵(hanxin)

    相传韩信才智过人,从不直接清点自己军队的人数,只要让士兵先后以三人一排.五人一排.七人一排地变换队形,而他每次只掠一眼队伍的排尾就知道总人数了.输入包含多组数据,每组数据包含3个非负整数a,b,c,表 ...

  5. java7 - JDK

    一.学习大纲: 1. 熟练使用 JDK 文档 2. 软件包 java.lang 提供利用 Java 编程语言进行程序设计的基础类. 3. 软件包 java.math 提供用于执行任意精度整数算法 (B ...

  6. 【Elasticsearch全文搜索引擎实战】之集群搭建及配置

    文中Elasticsearch版本为6.0.1 1. 环境配置 把环境配置放在第一节来讲,是因为很多人按官网的Getting Started安装运行会有各种错误.其实都是因为一些配置不正确引起的. 首 ...

  7. Selenium里可以自行封装与get_attribute对应的set_attribute方法

    我们在做UI自动化测试的过程中,某些情况会遇到,需要操作WebElement属性的情况. 假设现在我们需要获取一个元素的title属性,我们可以先找到这个元素,然后利用get_attribute方法获 ...

  8. 科普 TLS 1.3—新特性与开启方式

    TLS 1.3 协议针对安全强化及效率提升等方面进行了大量修改,相继推出 20 多个草案版本,即将完成最终的标准化.标准完成后,OpenSSL 组织将推出 OpenSSL 1.1.1 版本,对 TLS ...

  9. Mybatis认识

    MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis .iB ...

  10. mysql varchar vs oracle varchar2

    1.错误提示: mysql的Data truncation: Data too long for column 'path' at row 1 错误原因: 1.字段过长而导致出错的, 2. 可能是因为 ...