创建型模式
概述
《设计模式》一书,正式和详细地描述了23种基本的设计模式。它将这些设计模式划分为3类:创建型模式,行为型模式,结构型模式。
创建类模式
简述 主要关注对象的创建过程,将对象的创建过程进行封装,使客户端可以直接得到对象,而不用去关心如何创建对象。创建类模式有5种,分别是:
- 单例模式:用于得到内存中的唯一对象。
- 工厂方法模式:用于创建复杂对象。
- 抽象工厂模式:用于创建一组相关或相互依赖的复杂对象。
- 建造者模式:用于创建模块化的更加复杂的对象。
- 原型模式:用于得到一个对象的拷贝。
为什么需要创建性模式
首先,在编程中,对象的创建通常是一件比较复杂的事,因为,为了达到降低耦合的目的,我们通常采用面向抽象编程的方式,对象间的关系不会硬编码到类中,而是等到调用的时候再进行组装,这样虽然降低了对象间的耦合,提高了对象复用的可能,但在一定程度上将组装类的任务都交给了最终调用的客户端程序,大大增加了客户端程序的复杂度。采用创建类模式的优点之一就是将组装对象的过程封装到一个单独的类中,这样,既不会增加对象间的耦合,又可以最大限度的减小客户端的负担。
其次,使用普通的方式创建对象,一般都是返回一个具体的对象,即所谓的面向实现编程,这与设计模式原则是相违背的。采用创建类模式则可以实现面向抽象编程。客户端要求的只是一个抽象的类型,具体返回什么样的对象,由创建者来决定。
再次,可以对创建对象的过程进行优化,客户端关注的只是得到对象,对对象的创建过程则不关心,因此,创建者可以对创建的过程进行优化,例如在特定条件下,如果使用单例模式或者是使用原型模式,都可以优化系统的性能。
总结 所有的创建类模式本质上都是对对象的创建过程进行封装。
#
1.单例模式
定义:
确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
1.1枚举类单例
public enum EasySingleton{
INSTANCE;
}
我通过EasySingleton.INSTANCE来访问实例。 创建枚举默认就是线程安全的 而且还能防止反序列化导致重新创建新的对象。
1.2静态内部类
也是一种懒加载模式
public class Singleton {
/* 私有构造方法,防止被实例化 */
private Singleton() {
}
/* 此处使用一个内部类来维护单例 */
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
/* 获取实例 */
public static Singleton getInstance() {
return SingletonHolder.instance;
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve() {
return getInstance();
}
}
这种写法仍然使用JVM本身机制保证了线程安全问题;由于SingletonHander是私有的,除了getInstance()之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖与JDK版本。
饿汉式
这种方法非常简单,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。
public class Singleton{
//类加载时初始化
private static final Singleton instance=new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
这种写法如果完美的话,就没必要在啰嗦那么多双检锁的问题了。缺点是它不是一种懒加载模式(lazy initialization),单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。
单例模式的优点
- 在内存中只有一个对象,节省内存空间。
- 避免频繁的创建销毁对象,可以提高性能
- 避免对共享资源的多重占用
- 可以全局访问
使用场景
- 所有要求只有一个对象的场景 需要频繁实例化然后销毁的对象
- 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
- 有状态的工具类对象
- 频繁访问数据库或文件的对象
2.工厂模式
定义:
需要创建复杂对象的地方,适合使用工厂模式,通过工厂来创建对象实例,用于降低对象耦合性,将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
工厂模式分为三类:
- 简单工厂模式(Simple Factory)
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
这三种模式从上到下逐步抽象,并且更具一般性。
三种模式应用场景类似,这里着重介绍简单工厂模式,并通过示例介绍java反射机制与工厂模式的结合。
简单工厂模式:
又称为静态工厂方法模式,存在的目的很简单:定义一个用于创建对象的接口。 在简单工厂模式中,一个工厂类处于对产品类实例化调用的中心位置上,它决定那一个产品类应当被实例化, 如同一个交通警察站在来往的车辆流中,决定放行那一个方向的车辆向那一个方向流动一样。
组成有三个角色: 1) 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑。在java中它往往由一个具体类实现。 2) 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在java中由接口或者抽象类来实现。 3) 具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现。
简单工厂模式示例
interface Fruit{
public void eat();
}
class Apple implements Fruit{
public void eat(){
System.out.println("吃苹果。");
}
}
class Orange implements Fruit{
public void eat(){
System.out.println("吃橘子");
}
}
class Factory{ //工厂类
public static Fruit getInstance(String className){ //注意这里为静态方法
Fruit f=null;
if(className.equals("apple")){
f=new Apple();
}
if(className.endsWith("orange")){
f=new Orange();
}
return f;
}
}
public class FactoryDemo02 {
public static void main(String args[]){
Fruit f=Factory.getInstance("apple");
f.eat();
}
}
使用java反射来修改Factory工厂类:
interface Fruit {
public void eat();
}
class Apple implements Fruit {
public void eat() {
System.out.println("吃苹果。");
}
}
class Orange implements Fruit {
public void eat() {
System.out.println("吃橘子");
}
}
class Factory {
public static Fruit getInstance(String className) {
Fruit f = null;
try {
f = (Fruit) Class.forName(className).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return f;
}
}
public class CopyOfFactoryDemo03 {
public static void main(String args[]) {
Fruit f = Factory.getInstance("org1.Apple");
f.eat();
}
}
由此利用java的反射机制,就能动态的实例化各种类了。 但是这个程序还是存在一个问题,就是主函数这里需要填入一个完整的类名称,不够方便,所以要增加配置文件来简化,这部分内容就不举例了,主要就是通过配置文件来获取要实例化类的完整类名,传给工厂以便于创建该类的对象
而spring里面的ioc就是利用了工厂模式+java反射,下面通过示例来说明spring配置文件
简单的spring配置文件测试: Person类
package test2;
public class Person {
private String name;
private int age;
private Grade grade; //Grade对象作为其属性
public String getName() {
return name;
}
public Grade getGrade() {
return grade;
}
public void setGrade(Grade grade) {
this.grade = grade;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public int getTotleGrade() {
return grade.getEnglish()+grade.getMath();
}
}
Grade类
package test2;
public class Grade {
private int math;
private int english;
public int getMath() {
return math;
}
public void setMath(int math) {
this.math = math;
}
public int getEnglish() {
return english;
}
public void setEnglish(int english) {
this.english = english;
}
}
Bean.xml配置文件(该文件只要放在test2包里面就好了)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="Person" class="test2.Person">//第一个bean,是一个Person类,id名字随便取,还要写上类的全名
<property name="name">//下面开始把这个类里面的所有属性列出来,并赋值
<value>小龙</value>//这里的名字是通过程序里面的**set**来赋值的,不信你去掉程序里面相关的set,就出错了
</property>
<property name="age">
<value>23</value>
</property>
<property name="grade">//这里有点特别,这个grade变量是一个对象,和一般的变量要区别对待
<ref local="Grade"/>//这里指向了本配置文件里面一个名字叫Grade(即id=Grade)的bean
</property>
</bean>
<bean id="Grade" class="test2.Grade">//同上
<property name="math">
<value>99</value>
</property>
<property name="english">
<value>59</value>
</property>
</bean>
</beans>
最后是Test测试类
package test2;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import test.ExampleBean;
public class Test {
public static void main(String args[]){
Resource input = new ClassPathResource("test2/Bean.xml");//Bean.xml的路径
System.out.println("resource is:" + input);
BeanFactory factory = new XmlBeanFactory(input);//把input扔到工厂里面去,这个工厂就能为你提供实例了(我也不知道能不能这样说)
Person person =(Person) factory.getBean("Person");//你要一个叫Person的东西,那好,工厂就去找“Person"给你
Grade grade=(Grade)factory.getBean("Grade");
System.out.println("姓名:"+person.getName());//person可以调用里面相关的方法,就相当于new了一个Person一样
System.out.println("年龄:"+person.getAge());
System.out.println("数学成绩:"+grade.getMath());
System.out.println("英语成绩:"+grade.getEnglish());
System.out.println("数学,英语总成绩:"+person.getTotleGrade());
}
}
工厂方法模式
工厂方法模式组成:
1)抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。 2)具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。 3)抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。 4)具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。
产品类:
abstract class BMW {
public BMW(){
}
}
public class BMW320 extends BMW {
public BMW320() {
System.out.println("制造-->BMW320");
}
}
public class BMW523 extends BMW{
public BMW523(){
System.out.println("制造-->BMW523");
}
}
工厂类
interface FactoryBMW {
BMW createBMW();
}
public class FactoryBMW320 implements FactoryBMW{
@Override
public BMW320 createBMW() {
return new BMW320();
}
}
public class FactoryBMW523 implements FactoryBMW {
@Override
public BMW523 createBMW() {
return new BMW523();
}
}
客户类:
public class Customer {
public static void main(String[] args) {
FactoryBMW320 factoryBMW320 = new FactoryBMW320();
BMW320 bmw320 = factoryBMW320.createBMW();
FactoryBMW523 factoryBMW523 = new FactoryBMW523();
BMW523 bmw523 = factoryBMW523.createBMW();
}
}
可以看到,工厂方法模式中去掉了简单工厂方法的静态属性,并且有一个工厂类的接口(抽象类),使得可以根据不同需求,产生不同的工厂实现类(子类) 然而,当产品种类非常多时,会出现大量的与之对应的工厂对象,这不是我们所希望的。
3.抽象工厂方法
抽象工厂模式是工厂方法模式的升级版本,他用来创建一组相关或者相互依赖的对象。与工厂方法模式的区别就在于,工厂方法模式针对的是一个产品等级结构;而抽象工厂模式则是针对的多个产品等级结构。在编程中,通常一个产品结构,表现为一个接口或者抽象类,也就是说,工厂方法模式提供的所有产品都是衍生自同一个接口或抽象类,而抽象工厂模式所提供的产品则是衍生自不同的接口或抽象类。 在抽象工厂模式中,有一个产品族的概念:所谓的产品族,是指位于不同产品等级结构中功能相关联的产品组成的家族。抽象工厂模式所提供的一系列产品就组成一个产品族;而工厂方法提供的一系列产品称为一个等级结构。我们依然拿生产汽车的例子来说明他们之间的区别。
在上面的类图中,两厢车和三厢车称为两个不同的等级结构;而2.0排量车和2.4排量车则称为两个不同的产品族。再具体一点,2.0排量两厢车和2.4排量两厢车属于同一个等级结构,2.0排量三厢车和2.4排量三厢车属于另一个等级结构;而2.0排量两厢车和2.0排量三厢车属于同一个产品族,2.4排量两厢车和2.4排量三厢车属于另一个产品族。
明白了等级结构和产品族的概念,就理解工厂方法模式和抽象工厂模式的区别了,如果工厂的产品全部属于同一个等级结构,则属于工厂方法模式;如果工厂的产品来自多个等级结构,则属于抽象工厂模式。在本例中,如果一个工厂模式提供2.0排量两厢车和2.4排量两厢车,那么他属于工厂方法模式;如果一个工厂模式是提供2.4排量两厢车和2.4排量三厢车两个产品,那么这个工厂模式就是抽象工厂模式,因为他提供的产品是分属两个不同的等级结构。当然,如果一个工厂提供全部四种车型的产品,因为产品分属两个等级结构,他当然也属于抽象工厂模式了。
示例
//发动机以及型号
public interface Engine {
}
public class EngineA extends Engine{
public EngineA(){
System.out.println("制造-->EngineA");
}
}
public class EngineBextends Engine{
public EngineB(){
System.out.println("制造-->EngineB");
}
}
//空调以及型号
public interface Aircondition {
}
public class AirconditionA extends Aircondition{
public AirconditionA(){
System.out.println("制造-->AirconditionA");
}
}
public class AirconditionB extends Aircondition{
public AirconditionB(){
System.out.println("制造-->AirconditionB");
}
}
创建工厂类:
//创建工厂的接口
public interface AbstractFactory {
//制造发动机
public Engine createEngine();
//制造空调
public Aircondition createAircondition();
}
//为宝马320系列生产配件
public class FactoryBMW320 implements AbstractFactory{
@Override
public Engine createEngine() {
return new EngineA();
}
@Override
public Aircondition createAircondition() {
return new AirconditionA();
}
}
//宝马523系列
public class FactoryBMW523 implements AbstractFactory {
@Override
public Engine createEngine() {
return new EngineB();
}
@Override
public Aircondition createAircondition() {
return new AirconditionB();
}
}
客户:
public class Customer {
public static void main(String[] args){
//生产宝马320系列配件
FactoryBMW320 factoryBMW320 = new FactoryBMW320();
factoryBMW320.createEngine();
factoryBMW320.createAircondition();
//生产宝马523系列配件
FactoryBMW523 factoryBMW523 = new FactoryBMW523();
factoryBMW320.createEngine();
factoryBMW320.createAircondition();
}
}
通过代码可以看到每个具体工厂类可以创建多个具体产品类的实例
适用场景:
工厂与抽象工厂的区别
工厂方法模式:
一个抽象产品类,可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类只能创建一个具体产品类的实例。
抽象工厂模式:
多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类可以创建多个具体产品类的实例。
区别:
工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。
工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。
原型模式
定义: 用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象 类型: 创建类模式
类图:
原型模式主要用于对象的复制,它的核心就是类图中的原型类Prototype。Prototype类需要具备以下两个条件:
- 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。
代码实现:
class Prototype implements Cloneable {
public Prototype clone(){
Prototype prototype = null;
try{
prototype = (Prototype)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return prototype;
}
}
class ConcretePrototype extends Prototype{
public void show(){
System.out.println("原型模式实现类");
}
}
public class Client {
public static void main(String[] args){
ConcretePrototype cp = new ConcretePrototype();
for(int i=0; i< 5; i++){
ConcretePrototype clonecp = (ConcretePrototype)cp.clone();
clonecp.show();
}
}
}
/*
原型模式实现类
原型模式实现类
原型模式实现类
原型模式实现类
原型模式实现类
*/
原型模式的优点及适用场景
使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。
使用原型模式的另一个好处是简化对象的创建,使得创建对象就像我们在编辑文档时的复制粘贴一样简单。
因为以上优点,所以在需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。
原型模式的注意事项
使用原型模式复制对象不会调用类的构造方法。因为对象的复制是通过调用Object类的clone方法来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。还记得单例模式吗?单例模式中,只要将构造方法的访问权限设置为private型,就可以实现单例。但是clone方法直接无视构造方法的权限,所以,单例模式与原型模式是冲突的,在使用时要特别注意。
深拷贝与浅拷贝。Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。例如:
public class Prototype implements Cloneable {
private ArrayList list = new ArrayList();
public Prototype clone(){
Prototype prototype = null;
try{
prototype = (Prototype)super.clone();
prototype.list = (ArrayList) this.list.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return prototype;
}
}
由于ArrayList不是基本类型,所以成员变量list,不会被拷贝,需要我们自己实现深拷贝,幸运的是java提供的大部分的容器类都实现了Cloneable接口。所以实现深拷贝并不是特别困难。
PS:深拷贝与浅拷贝问题中,会发生深拷贝的有java中的8中基本类型以及他们的封装类型,另外还有String类型。其余的都是浅拷贝。