黑马程序员-Java设计模式
2016-03-15 21:17
387 查看
——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
目录:
1. 生成器模式
2. 策略模式
3. 模板模式
4. 工厂模式
5. 装饰模式
6. 享元模式
7. 单例模式
1. 生成器模式(Builder Pattern)
例如下面的Student类,实例字段很多,这样如果在构造器中创建一个对象的话非常的麻烦,需要传入多达7个参数。遇到这种场景,我们可以使用构造器模式来优化对象的创建。
在构造器模式中,原来的类的创建任务被委托到一个伴随类中,例如下面是一个简单的构造器模式的使用,它改进了上面的Student类的创建。
在Student类中定义了一个内部类Builder,它包含了外部类的所有字段,调用Builder类的withXXX方法可以对参数进行设置,由于这些方法返回了构建者本身,所以可以采用’链式’编程。最后调用build方法可以创建一个外围类对象,build方法内部调用了外围类的构造方法,并把参数传递给构造器,最后返回这个对象。
2. 策略模式(Strategy Pattern)
使用策略模式可以很方便的替换某个算法的具体实现,而不需要重写代码。
代码示例:
下面的日志记录器中,存在两个具体实现,一个将日志消息打印到控制台,另一个将消息保存到文件中。
在下面的简单测试中,我们可以方便的替换日志记录器的具体实现:
3. 模板模式(Template Pattern)
模板模式是把一个算法的部分或全部步骤推出或委托给一个子类。公共的行为在超类中定义。
代码示例:
下面的StringList类中定义了一个包含字符串的列表,包含一个过滤方法,传入一个
StringFilter对象,就能对字符串列表进行过滤并返回过滤后的列表。StringFilter是一个接口,它实现了模板模式,我们可以定义不同的过滤功能。
需要注意的是,filter方法中,遍历了字符串列表的每一个元素,然后根据一个谓词判断元素的值是否应该包含在被返回的列表中。这个方法把过滤算法的判断步骤委托给StringFilter对象,它把判定某个值的逻辑和过滤字符串列表算法完全分离开了。
下面演示了使用模板的两种实现,一种什么也不做,直接返回true;另一个方法根据字符串的长度判断,如果大于3则返回true。
在上例中,将模板中的算法分离开来有很多好处,使用者可以根据具体的要求来实现它,同时又不需要对StringList类作任何的修改。
4. 工厂模式
工厂模式是将大量有共同接口的类实例化。工厂模式可以动态决定将哪一个类实例化而不必知道每次要实例化哪一个类。它分为简单工厂模式、工厂方法模式和抽象工厂模式三种。下面具体介绍:
(1)简单工厂模式
简单工厂模式又称为静态方法模式,属于类创建型模式。由于工厂模式比较复杂,这里介绍下它包含的三个角色:
工厂类(核心):用于创建其他类的实例,包含判断逻辑以判断要创建哪一个具体类;
抽象产品类:工厂类创建的所有产品的超类或他们共同的接口;
具体产品类:工厂方法创建的具体产品实例。
代码示例:
下面的示例中Factory 是工厂类,包含了创建Car实例的静态方法;Car接口是抽象产品类,定义了公共功能;Vw和Bmw是具体的产品类,工厂模式创建的所有产品都是他们的实例:
简单工厂模式适合创建的类比较少的场景,因为他所有的产品判断逻辑都包含在工厂的静态方法中。在增加产品时,需要增加一个具体产品类和修改工厂方法的判断逻辑。在使用的时候,客户端无需知道具体产品类和创建细节,只需要提供参数即可。
(2)工厂方法模式
该模式中定义了一个工厂类接口,包含一个创建对象的抽象方法。通过子类来决定生产哪一种具体产品。在工厂方法模式中,核心的工厂类不再创建具体产品,而是把工作交给子类,核心类只提供接口。
工厂方法模式包含的角色:
抽象工厂类:工厂模式中的任何工厂类都要实现它,定义了创建产品的公共方法;
具体工厂类:实现了抽象工厂的具体工厂类。含有创建产品的实例方法;
抽象产品类:工厂类创建的所有产品的超类或他们共同的接口;
具体产品类:工厂方法创建的具体产品实例。
下面的示例中,Factory 是抽象工厂类,Car是抽象产品类,Bmw 和Vw是具体的产品类,BmwFactory和VwFactory 是对应的工厂类 :
在下面的测试中,我们使用了工厂类来创建对象。可以看到多态特性是具体工厂类的能创建对应的产品的关键。
(5)装饰模式(Decorator Pattern)
装饰模式就是给原有的对象修改或配置功能。装饰模式中,装饰者和被装饰的对象都应实现同一个接口。使用装饰模式可以进行功能的组装,例如JavaIO中就大量应用了装饰模式,比如我们可以把一个输入文件字节流对象传入一个缓冲字节流中组合成带有缓冲输出功能的文件字节流。
具体代码:
下面的Book是一个接口,被装饰类NoteBook和装饰类ColorBook 都实现了这个接口,最后在装饰类的main测试方法中演示了使用装饰模式创建一个ColorBook 实例的步骤。
}
6. 享元模式(Flyweight Pattern)
享元模式适用于多个对象共享一个值的情况。它的前提是对象的值不可变,否则一个对象修改值后所有共享该值的变量都会受到影响。
Java中使用了享元模式的有String类、基本类型包装类如Integer等。下面使用Integer来介绍享元模式。
Integer类型比较特殊,他包含一个int范围在-128~127的常量池,即Integer对象表示的int类型数值在-128~127之间时,会直接从常量池中返回,而不会新建Integer对象。
代码示例:
下面使用代码来证明常量池的存在,需要注意的是使用valueOf方法才会使用常量池中的数据,使用new关键字则是新建一个对象。下面第三组证明了这一点。
7. 单例模式(Singleton Pattern)
单例模式是指一个类只允许创建一个实例,下面是一个具体示例。
由于单例类对象的获取方法采用了同步方法,这样在频繁获取对象的时候会降低性能,所以可以考虑把创建过程分离开,并只对它进行同步保护。下面是改进后的代码:
目录:
1. 生成器模式
2. 策略模式
3. 模板模式
4. 工厂模式
5. 装饰模式
6. 享元模式
7. 单例模式
1. 生成器模式(Builder Pattern)
例如下面的Student类,实例字段很多,这样如果在构造器中创建一个对象的话非常的麻烦,需要传入多达7个参数。遇到这种场景,我们可以使用构造器模式来优化对象的创建。
public class Student { private final String name; private final int age; private final int number; private final String major; private final String address; private String email; private int phone; public Student(private String name, private int age, private int number, private String major,private String address, private String email, private int phone) { super(); this.name = name; this.age = age; this.number = number; this.major = major; this.address = address; this.email = email; this.phone = phone; } }
在构造器模式中,原来的类的创建任务被委托到一个伴随类中,例如下面是一个简单的构造器模式的使用,它改进了上面的Student类的创建。
在Student类中定义了一个内部类Builder,它包含了外部类的所有字段,调用Builder类的withXXX方法可以对参数进行设置,由于这些方法返回了构建者本身,所以可以采用’链式’编程。最后调用build方法可以创建一个外围类对象,build方法内部调用了外围类的构造方法,并把参数传递给构造器,最后返回这个对象。
public class Student { private final String name; private final int age; private final int number; private final String major; private final String address; private String email; private long phone; public static class Builder { private String name; private int age; private int number; private String major; private String address; private String email; private long phone; // 伴随方法,返回构建者,可以采用'链式'编程 public Builder withName(final String name) { this.name = name; return this; } public Builder withAge(final int age) { this.age = age; return this; } public Builder withNumber(final int number) { this.number = number; return this; } public Builder withMajor(final String major) { this.major = major; return this; } public Builder withAddress(final String address) { this.address = address; return this; } public Builder withEmail(final String email) { this.email = email; return this; } public Builder withPhone(final long phone) { this.phone = phone; return this; } // 伴随类创建Student类对象的方法,方法中首先对必须设置的参数进行检查,如果符合条件,那么调用Student类的构造方法,创建一个对象,如果不符合抛出异常。 public Student build() throws IllegalStateException { if (name != null && age != 0 && number != 0 && major != null && address != null) { return new Student(name, age, number, major, address, email, phone); } else { throw new IllegalStateException("cannot create Pet"); } } } // 构造方法设置为私有权限,只能通过伴随类来调用 private Student(final String name, final int age, final int number, final String major, final String address, final String email, final long phone) { super(); this.name = name; this.age = age; this.number = number; this.major = major; this.address = address; this.email = email; this.phone = phone; } public static void main(String[] args) { //测试,创建构造器对象 Student.Builder builder = new Builder(); Student xiaoming = builder .withName("xiaoming") .withNumber(250) .withMajor("Math") .withAddress("Beijing") .build(); } }
2. 策略模式(Strategy Pattern)
使用策略模式可以很方便的替换某个算法的具体实现,而不需要重写代码。
代码示例:
下面的日志记录器中,存在两个具体实现,一个将日志消息打印到控制台,另一个将消息保存到文件中。
//用于日志记录的接口 public interface Logging { public abstract void write(String msg); } //实现了Logging接口的ConsoleLogging日志记录类,它将日志消息打印到控制台 public class ConsoleLogging implements Logging { @Override public void write(String msg) { System.out.println(msg); } } //实现了Logging接口的日志记录类,它将日志消息保存到文件中 public class FileLogging implements Logging { // 用于保存日志的本地文件 private File location; public FileLogging(File location) throws IOException { this.location = location; } @Override public void write(String msg) { // 打印流,用于将数据写入文件,使用try-with-resources语句确保资源关闭 try (PrintWriter pw = new PrintWriter(new FileWriter(location, true), true)) { pw.println(msg); } catch (IOException e) { e.printStackTrace(); } } }
在下面的简单测试中,我们可以方便的替换日志记录器的具体实现:
public class Test { public static void main(String[] args) throws IOException { Logging log = new ConsoleLogging(); //可以把日志记录器替换为保存到文件中的实现 //log = new FileLogging(new File("d:\\testLogging.txt")); for (int i = 0; i < 100; i++) { if (i % 3 == 0) { //记录100以内的能被3整除的int类型整数 log.write("" + i); } } } }
3. 模板模式(Template Pattern)
模板模式是把一个算法的部分或全部步骤推出或委托给一个子类。公共的行为在超类中定义。
代码示例:
下面的StringList类中定义了一个包含字符串的列表,包含一个过滤方法,传入一个
StringFilter对象,就能对字符串列表进行过滤并返回过滤后的列表。StringFilter是一个接口,它实现了模板模式,我们可以定义不同的过滤功能。
需要注意的是,filter方法中,遍历了字符串列表的每一个元素,然后根据一个谓词判断元素的值是否应该包含在被返回的列表中。这个方法把过滤算法的判断步骤委托给StringFilter对象,它把判定某个值的逻辑和过滤字符串列表算法完全分离开了。
import java.util.LinkedList; import java.util.List; public class StringList { private List<String> list; public StringList(List<String> list) { this.list = list; } public List filter(StringFilter filter) { List<String> toReturn = new LinkedList<String>(); for (String element : list) { if (filter.isValid(element)) toReturn.add(element); } return toReturn; } } //这是一个模板,定义了一个判断字符串是否满足条件的方法,子类可以定义具体的行为 public interface StringFilter { boolean isValid(String str); }
下面演示了使用模板的两种实现,一种什么也不做,直接返回true;另一个方法根据字符串的长度判断,如果大于3则返回true。
import java.util.LinkedList; import java.util.List; public class StringListTest { public static void main(String[] args) { //定义一个字符串列表 List<String> list = new LinkedList<String>(); list.add("java"); list.add("android"); list.add("ios"); list.add("c"); list.add("windows"); list.add("sql"); // 创建一个测试对象 StringList sl = new StringList(list); //使用匿名内部类的模板实现 StringFilter nonFilter = new StringFilter() { // 没有过滤任何东西 @Override public boolean isValid(String str) { return true; } }; //测试 List test1 = sl.filter(nonFilter); //使用lambda expression遍历列表,发现没有过滤任何元素 test1.forEach(element -> System.out.println(element)); //另一个模板实现,根据字符串长度进行过滤 StringFilter lengthFilter = new StringFilter() { // 字符串长度超过3的才能返回true @Override public boolean isValid(String str) { if (str.length() > 3) return true; return false; } }; List test2 = sl.filter(lengthFilter); //结果打印了三个字符串长度大于3的元素 test2.forEach(element -> System.out.println(element)); } }
在上例中,将模板中的算法分离开来有很多好处,使用者可以根据具体的要求来实现它,同时又不需要对StringList类作任何的修改。
4. 工厂模式
工厂模式是将大量有共同接口的类实例化。工厂模式可以动态决定将哪一个类实例化而不必知道每次要实例化哪一个类。它分为简单工厂模式、工厂方法模式和抽象工厂模式三种。下面具体介绍:
(1)简单工厂模式
简单工厂模式又称为静态方法模式,属于类创建型模式。由于工厂模式比较复杂,这里介绍下它包含的三个角色:
工厂类(核心):用于创建其他类的实例,包含判断逻辑以判断要创建哪一个具体类;
抽象产品类:工厂类创建的所有产品的超类或他们共同的接口;
具体产品类:工厂方法创建的具体产品实例。
代码示例:
下面的示例中Factory 是工厂类,包含了创建Car实例的静态方法;Car接口是抽象产品类,定义了公共功能;Vw和Bmw是具体的产品类,工厂模式创建的所有产品都是他们的实例:
public class Factory { public static Car makeCar(String carName) { if (carName.equalsIgnoreCase("vw")) { return new Vw(); } if (carName.equalsIgnoreCase("bmw")) { return new Bmw(); } else return null; } } public interface Car { void drive(); } public class Vw implements Car { @Override public void drive() { System.out.println("正在开一辆大众汽车"); } } public class Bmw implements Car { @Override public void drive() { System.out.println("正在开一辆宝马汽车"); } }
简单工厂模式适合创建的类比较少的场景,因为他所有的产品判断逻辑都包含在工厂的静态方法中。在增加产品时,需要增加一个具体产品类和修改工厂方法的判断逻辑。在使用的时候,客户端无需知道具体产品类和创建细节,只需要提供参数即可。
(2)工厂方法模式
该模式中定义了一个工厂类接口,包含一个创建对象的抽象方法。通过子类来决定生产哪一种具体产品。在工厂方法模式中,核心的工厂类不再创建具体产品,而是把工作交给子类,核心类只提供接口。
工厂方法模式包含的角色:
抽象工厂类:工厂模式中的任何工厂类都要实现它,定义了创建产品的公共方法;
具体工厂类:实现了抽象工厂的具体工厂类。含有创建产品的实例方法;
抽象产品类:工厂类创建的所有产品的超类或他们共同的接口;
具体产品类:工厂方法创建的具体产品实例。
下面的示例中,Factory 是抽象工厂类,Car是抽象产品类,Bmw 和Vw是具体的产品类,BmwFactory和VwFactory 是对应的工厂类 :
public interface Factory { Car makeCar(); } public interface Car { void drive(); } public class Bmw implements Car { @Override public void drive() { System.out.println("正在开一辆宝马汽车"); } } public class Vw implements Car { @Override public void drive() { System.out.println("正在开一辆大众汽车"); } } public class BmwFactory implements Factory { @Override public Car makeCar() { return new Bmw(); } } public class VwFactory implements Factory { @Override public Car makeCar() { return new Vw(); } }
在下面的测试中,我们使用了工厂类来创建对象。可以看到多态特性是具体工厂类的能创建对应的产品的关键。
public class CarMakeTest { public static void main(String[] args) { Factory bmwFactory=new BmwFactory(); Car bmw=bmwFactory.makeCar(); bmw.drive(); Factory vwFactory=new VwFactory(); Car vwCar=vwFactory.makeCar(); vwCar.drive(); } }
(5)装饰模式(Decorator Pattern)
装饰模式就是给原有的对象修改或配置功能。装饰模式中,装饰者和被装饰的对象都应实现同一个接口。使用装饰模式可以进行功能的组装,例如JavaIO中就大量应用了装饰模式,比如我们可以把一个输入文件字节流对象传入一个缓冲字节流中组合成带有缓冲输出功能的文件字节流。
File toOpen=new File("d:\\anyFile"); FileOutputStream fos=new FileOutputStream(toOpen);//文件字节输出流 BufferedOutputStream bos=new BufferedOutputStream(fos);//缓冲字节输出流
具体代码:
下面的Book是一个接口,被装饰类NoteBook和装饰类ColorBook 都实现了这个接口,最后在装饰类的main测试方法中演示了使用装饰模式创建一个ColorBook 实例的步骤。
public interface Book { void write(); } public class NoteBook implements Book{ @Override public void write() { System.out.println("book"); } } public class ColorBook implements Book { private Book target; public ColorBook(Book target) { super(); this.target = target; } @Override public void write() { System.out.print("white "); target.write(); } public static void main(String[] args) { Book b=new NoteBook(); b.write();//原始方法打印结果:book ColorBook cb=new ColorBook(b); cb.write();//装饰后的方法打印结果:white book }
}
6. 享元模式(Flyweight Pattern)
享元模式适用于多个对象共享一个值的情况。它的前提是对象的值不可变,否则一个对象修改值后所有共享该值的变量都会受到影响。
Java中使用了享元模式的有String类、基本类型包装类如Integer等。下面使用Integer来介绍享元模式。
Integer类型比较特殊,他包含一个int范围在-128~127的常量池,即Integer对象表示的int类型数值在-128~127之间时,会直接从常量池中返回,而不会新建Integer对象。
代码示例:
下面使用代码来证明常量池的存在,需要注意的是使用valueOf方法才会使用常量池中的数据,使用new关键字则是新建一个对象。下面第三组证明了这一点。
public class Test { public static void main(String[] args) { //常量池中的引用直接返回 Integer i1 = Integer.valueOf(127); Integer i2 = Integer.valueOf(127); System.out.println(i1 == i2);//true //常量池有范围 Integer i3 = Integer.valueOf(128); Integer i4 = Integer.valueOf(128); System.out.println(i3 == i4);//false //使用构造器不会用到常量池 Integer i5=new Integer(127); Integer i6=new Integer(127); System.out.println(i5 == i6);//false } }
7. 单例模式(Singleton Pattern)
单例模式是指一个类只允许创建一个实例,下面是一个具体示例。
public class SingletonPattern { //私有化构造器 private SingletonPattern() {} //提供一个静态SingletonPattern类的类字段,初始化为空,当客户端调用获取方法后才实例化,这种方式成为迟加载,可以减少对象的创建,提升性能 private static SingletonPattern alone = null; //提供静态的获取单例对象的方法,注意该方法必须使用同步机制对共享的变量alone进行保护,同时进行了迟加载方法,只有在第一次调用该方法时,类变量alone才会初始化 public static synchronized SingletonPattern getInstance() { if (alone == null) { alone = new SingletonPattern(); return alone; } else { return alone; } } public static void main(String[] args) { //测试成功创建 SingletonPattern myInstance = SingletonPattern.getInstance(); } }
由于单例类对象的获取方法采用了同步方法,这样在频繁获取对象的时候会降低性能,所以可以考虑把创建过程分离开,并只对它进行同步保护。下面是改进后的代码:
public class SingletonPattern { private SingletonPattern() { } private static SingletonPattern alone = null; public static SingletonPattern getInstance() { if (alone == null) { initSingle(); return alone; } else { return alone; } } // 初始化alone变量的同步方法,只会被调用一次 private synchronized static void initSingle() { if (alone == null) alone = new SingletonPattern(); } public static void main(String[] args) { // 测试成功创建 SingletonPattern myInstance = SingletonPattern.getInstance(); } }
相关文章推荐
- 网易游戏实习生面试
- 面试准备之Java常用的包
- 职场里根本没人管你怎么办?
- 《读书笔记》程序员的自我修养之编译和链接
- 面试准备之Java常用的设计模式
- 2016-春季校招面试笔试mark
- 图论面试题
- 一个大神程序员的使命感究竟应该是什么
- 为什么你投十份简历,只有一两家公司约你?又或者为什么你每投一份简历都能获得面试机会?
- 《程序员修炼之道》——第二章 注重实效的途径(三)
- 经典算法题一览
- 程序员如何优雅的挣零花钱?
- 成为高级程序员的 10 个步骤
- equal() 与hashcode()之我理解
- hibernate中Restrictions用法
- 剑指offer代码解析——面试题25二叉树中和为某一值的路径
- 剑指offer代码解析——面试题25二叉树中和为某一值的路径
- 毁灭程序员效率的 15 个障碍
- 据说年薪30万的Android程序员必须知道的帖子
- Android面试经验汇总(二)