从需求进行入手,可以更深入的理解什么是设计模式。
有一个制作披萨的需求:需要便于扩展披萨的种类,便于维护。
1.披萨的种类有很多:GreekPizz,CheesePizz等
2.披萨的制作流程:prepare(制作)=> bake(烘烤)=> cut(切开)=> box(打包)
3.完成披萨店的订购功能。
进行定义抽象披萨类,这个抽象类进行定义了name属性和抽象方法prepape,这个抽象方法主要是让继承的类去实现使用,定义了一些非抽象方法,这些非抽象方法,是派生类通用的代码,都可以进行使用的这些方法,并且表现出嗯对特征是相同的。
即将派生类需要重写表现出来不同特征的方法在抽象类中直接定义为abstract抽象方法,所有子类表现出来相同特征不需要进行定制化的代码直接定义为普通方法,放在抽象类中进行使用。
这里由于披萨的制作流程,prepare => bake => cut => box,整个过程中只有prepare的过程是不一样的,其它几个方法都是一样的,所以后面的方法都直接在抽象类中进行定义了,这些方法都直接在抽象类中进行实现了逻辑,将prepare定义为abstract方法,方法派生类进行实现。
/**
* 披萨类
*/
public abstract class Pizza {
protected String name;
public abstract void prepare();
// 烘烤
public void bake() {
System.out.println(name + " baking~~~");
}
// 切分
public void cut() {
System.out.println(name + " cutting~~~");
}
// 打包
public void box() {
System.out.println(name + " boxing~~~");
System.out.println("bing go 完成啦~~~");
}
public void setName(String name) {
this.name = name;
}
}
/**
* GreekPizza
*/
public class GreekPizza extends Pizza {
@Override
public void prepare() {
System.out.println("为希腊披萨 准备原材料");
}
}
/**
* 奶酪披萨
*/
public class CheesePizza extends Pizza {
@Override
public void prepare() {
System.out.println("给制作奶酪披萨 准备原材料");
}
}
这个类中进行封装了一个getType方法,这个方法主要是进行在控制台进行接收数据并返回。
这个类的主要逻辑封装在了Constructor中,进行循环在控制台中进行接收用户输入到控制台中的数据,并进行根据输入的数据进行采用一些逻辑操作,如果输入的披萨是有的就进行new一个相应的披萨对象,并进行走相应的制作流程,如果输入的披萨都不存在,就会break跳出循环,结束点单。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 订购披萨
*/
public class OrderPizza {
public OrderPizza() {
Pizza pizza = null;
// 订购披萨的类型
String orderType = null;
while (true) {
orderType = getType();
if (orderType.equals("greek")) {
pizza = new GreekPizza();
pizza.setName("希腊披萨");
} else if (orderType.equals("cheese")) {
pizza = new CheesePizza();
pizza.setName("奶酪披萨");
} else {
break;
}
// 输出pizza 制作过程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}
}
private String getType() {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza 种类");
try {
String str = bufferedReader.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
1.3.1优缺点的分析
优点就是比较好理解,便于进行操作。
缺点就是违反了OCP原则(开闭原则),对扩展(提供者)开放,对修改(使用者)关闭,从UML图中就可以进行分析出来,提供者就是Pizza类的派生类,对扩展开放的意思其实就是Pizza的派生类可以无限进行扩展,代码底层可以进行更改(只要外部调用的时候表现出来的特征一样就没问题),对修改关闭的意思就是,当Pizza进行扩展出新的派生类的时候,十一用者可以进行完美的无缝兼容,调用的方法无需进行改变,这就是OCP原则 => 至少要进行保证尽量少的进行修改代码。
假设现在进行增加一个新的Pizza派生类:PepperPizza。
此时进行的增加改动是遵循OCP开闭原则,对扩展进行开放。
/**
* 胡椒披萨
*/
public class PepperPizza extends Pizza {
@Override
public void prepare() {
System.out.println("给制作胡椒披萨 准备原材料");
}
}
但是如果我们想要在使用者OrderPizza中进行兼容使用,就需要在使用者OrderPizza中进行修改,这样就违反了OCP原则,对修改没有进行关闭。
public OrderPizza() {
Pizza pizza = null;
// 订购披萨的类型
String orderType = null;
while (true) {
orderType = getType();
if (orderType.equals("greek")) {
pizza = new GreekPizza();
pizza.setName("希腊披萨");
} else if (orderType.equals("cheese")) {
pizza = new CheesePizza();
pizza.setName("奶酪披萨");
} else if (orderType.equals("pepper")) {
pizza = new PepperPizza();
pizza.setName("胡椒披萨");
} else {
break;
}
// 输出pizza 制作过程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}
}
如果进行了扩展,可以接受对代码进行接单的修改(比如说对一处代码进行修改),但是我们往往不是只有一个OrderPizza下单的地方,假如说我们这些Pizza还需要在其它的地方进行使用,那么Pizza如果进行了扩展之后,就需要在多处进行修改,这样就太糟糕了。
主要问题:主要问题就是创建Pizza的代码往往有多处,Pizza进行扩展后需要在多处进行变动修改。
思路:将创建Pizza对象封装到一个类中(工厂类),当我们有新的Pizza类被扩展出的时候,只需要修改这个类即可,其它创建Pizza的代码就不需要进行修改了 => 简单工厂模式。
简单工厂模式就是来进行解决这个问题的,将Pizza对象的创建交给简单工厂类,这样就将创建Pizza对象的代码从各个地方进行解耦i。
1.简单工厂模式属于创造性模式,是工厂模式中的一种。简单工厂模式是由一个工厂对象决定创建出那哪一个产品类的实例。简单工厂模式是工厂模式家族最简单实用的模式。
2.简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)=> 其实就是将代码抽离到一个工厂类中,在工厂类中进行创建实例化对象。
3.在软件开发中,当我们会用到大量的创建某种,某类或者某批对象时,就会使用到工厂模式。
通过UML类设计图就可以分析出来Pizza派生出去的类都会依赖在SimpleFactory类中,也就是将所有Pizza类型的创建交给SimpleFactory简单工厂类进行处理,其它的需要创建Pizza对象的类,直接进行将SimpleFactory这个工厂类对象聚合进去使用即可。
也就是说将创建Pizza对象的权力交给了SimoleFactory工厂类了,需要创建对象直接找他即可,完成了对象创建的解耦。
其实就是将其它类创建Pizza对象的代码给集成到SimpleFactory简单工厂类中,并不仅仅局限于目前类中的createPizza方法,其它方法同样也是可以进行集成抽离的,我的理解是,只要是用到创建Pizza对象的代码都可以抽离到SimpleFactory中进行统一维护使用。
目前进行定义的createPizza的代码其实是一个比较健全的根据传入的类型创建不同Pizza派生类对象的代码,如果有对应type就会进行创建,如果没有就不创建,返回一个null。
/**
* 简单工厂类
*/
public class SimpleFactory {
public Pizza createPizza(String type) {
System.out.println("使用简单工厂模式");
Pizza pizza = null;
if (type.equals("greek")) {
pizza = new GreekPizza();
pizza.setName("希腊披萨");
} else if (type.equals("cheese")) {
pizza = new CheesePizza();
pizza.setName("奶酪披萨");
} else if (type.equals("pepper")) {
pizza = new PepperPizza();
pizza.setName("胡椒披萨");
}
return pizza;
}
}
其实就是使用聚合的方式将SimpleFactory简单工厂类集成到OrderPizza,并将创建Pizza对象的逻辑抽取到SimpleFactory中进行完成。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 订购披萨
*/
public class OrderPizza {
// 定义一个简单工厂对象
private SimpleFactory simpleFactory;
private Pizza pizza = null;
public void setSimpleFactory(SimpleFactory simpleFactory) {
this.simpleFactory = simpleFactory;
}
public void order() {
// 用户输出的
String orderType = "";
// 进行订购披萨
while (true) {
orderType = getType();
pizza = simpleFactory.createPizza(orderType);
// 输出Pizza
if (pizza != null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println("订购披萨失败!!!");
break;
}
}
}
private String getType() {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza 种类");
try {
String str = bufferedReader.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
现在进行点披萨的需求再次升级了,主要是下单的奶酪种类进行了进一步细分,客户在点披萨的时候可以进行点北京的奶酪Pizza,北京的胡椒Pizza或者伦敦的奶酪Pizza,伦敦的胡椒Pizza。
由于分类进一步细分了,使用一个SimpleFactoiy进行创建不同的Pizza,当然也可以进行根据地域分类的不同,再进行抽象创建出多个SimpleFactory,进行针对不同低于的Pizza使用不同的SimpleFactory。
但是考虑项目的规模,以及软件的可维护性,可扩展性,使用这个方式可能会有很多SimpleFactory,会降低可维护性和扩展性,感觉不是很好。
使用工厂方法模式可以解决这个问题。
将披萨项目的实例化Pizza的功能抽象为抽象方法,在不同的口味点餐子类中具体实现。
定义一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法将对象的实例化推迟到子类。
order和getrType都是所有工厂方法实现类都拥有的通用方法,这些方法直接定义在OrderPizza中即可。
每个工厂方法实现类的区别主要是在创建Pizza对象的逻辑不一样,不同的工厂方法实现类负责不同类别的Pizza实例对象的创建,所以要进行定义这个createPizza的抽象方法,让不同的工厂方法实现类去实现这个abstract方法。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 定义抽象方法的工厂 => 方便实现工厂实现实例化的方法
*/
public abstract class OrderPizza {
// 定义一个抽象, createPizza,
public abstract Pizza createPizza(String type);
// 下单方法
public void order() {
// 用户输出的
String orderType = "";
// 进行订购披萨
while (true) {
orderType = getType();
Pizza pizza = createPizza(orderType);
// 输出Pizza
if (pizza != null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println("订购披萨失败!!!");
break;
}
}
}
private String getType() {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza 种类");
try {
String str = bufferedReader.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
1.抽象工厂和模式:定义了一个interface用于创建相关或者有依赖关系的对象簇,而无需指明具体的类。
2.抽象工厂模式其实可以将简单工厂模式和工厂方法模式进行整合。
3.从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者说是进一步抽象)
4.将工厂抽象为两层:AbsFactory(抽象工厂)和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样单个工厂类就变为了工厂簇,更利于代码的维护和扩展。
这个的设计其实就是定义出了一个AbsFactory接口,抽象出来一个创建实例的工厂方法,让不同的子类去依赖不同Pizza实现类,去实现相应的方法。
然后让需要进行使用抽象工厂创建相应实例的类中,直接将抽象工厂方法类AbsFactory聚合进去即可,这样就实现了一层缓冲解耦合。
/**
* 抽象工厂的抽象接口
*/
public interface AbsFactory {
Pizza createPizza(String orderType);
}
/**
* 北京抽象工厂实现类
*/
public class BJFactory implements AbsFactory {
@Override
public Pizza createPizza(String orderType) {
System.out.println("使用抽象工厂模式");
Pizza pizza = null;
if (orderType.equals("greek")) {
pizza = new BJGreekPizza();
pizza.setName("北京希腊披萨");
} else if (orderType.equals("cheese")) {
pizza = new BJCheesePizza();
pizza.setName("北京奶酪披萨");
} else if (orderType.equals("pepper")) {
pizza = new BJPepperPizza();
pizza.setName("北京胡椒披萨");
}
return pizza;
}
}
其实就是通过一个字段,定义为抽象接口类型(这样就可以接收不同类型的抽象工厂实例类对象了)
使用setter方法进行给字段设置对应对象 => 从里面就可以体现出聚合。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 下单端(使用抽象工厂创建实例)
*/
public class OrderPizza {
private AbsFactory factory;
public void setFactory(AbsFactory factory) {
this.factory = factory;
}
public void order() {
// 用户输出的
String orderType = "";
// 进行订购披萨
while (true) {
orderType = getType();
Pizza pizza = factory.createPizza(orderType);
// 输出Pizza
if (pizza != null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println("订购披萨失败!!!");
break;
}
}
}
private String getType() {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza 种类");
try {
String str = bufferedReader.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
Calendar的源码主要是应用了简单工厂模式,主要是在创建对象的时候进行使用。
Calendar中的构造方法被protected化了,所以在外部是无法通过New关键字去创建Calendar对象的,但是Calendar类对外暴露了一个getInstance方法进行使用,Calendar类就是通过getInstance方法进行创建对象的。
可以看到Calendar方法确实被封印了,方法被标注为了protected,表示这个方法不能在类/派生类的外部进行使用。
/**
* Constructs a Calendar with the default time zone
* and the default {@link java.util.Locale.Category#FORMAT FORMAT}
* locale.
* @see TimeZone#getDefault
*/
protected Calendar()
{
this(TimeZone.getDefaultRef(), Locale.getDefault(Locale.Category.FORMAT));
sharedZone = true;
}
/**
* Constructs a calendar with the specified time zone and locale.
*
* @param zone the time zone to use
* @param aLocale the locale for the week data
*/
protected Calendar(TimeZone zone, Locale aLocale)
{
fields = new int[FIELD_COUNT];
isSet = new boolean[FIELD_COUNT];
stamp = new int[FIELD_COUNT];
this.zone = zone;
setWeekCountData(aLocale);
}
Calendar使用getIntance进行让外部创建对象,这就是Calendar进行暴露在外部创建对象的方法(getInstance方法)
Calendar主要也是在里面进行使用了简单工厂模式,进行创建对象。
接下来就来分析一下getInstance的源码。
/**
* Gets a calendar using the default time zone and locale. The
* Calendar
returned is based on the current time
* in the default time zone with the default
* {@link Locale.Category#FORMAT FORMAT} locale.
*
* @return a Calendar.
*/
public static Calendar getInstance()
{
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
/**
* Gets a calendar using the specified time zone and default locale.
* The Calendar
returned is based on the current time
* in the given time zone with the default
* {@link Locale.Category#FORMAT FORMAT} locale.
*
* @param zone the time zone to use
* @return a Calendar.
*/
public static Calendar getInstance(TimeZone zone)
{
return createCalendar(zone, Locale.getDefault(Locale.Category.FORMAT));
}
/**
* Gets a calendar using the default time zone and specified locale.
* The Calendar
returned is based on the current time
* in the default time zone with the given locale.
*
* @param aLocale the locale for the week data
* @return a Calendar.
*/
public static Calendar getInstance(Locale aLocale)
{
return createCalendar(TimeZone.getDefault(), aLocale);
}
/**
* Gets a calendar with the specified time zone and locale.
* The Calendar
returned is based on the current time
* in the given time zone with the given locale.
*
* @param zone the time zone to use
* @param aLocale the locale for the week data
* @return a Calendar.
*/
public static Calendar getInstance(TimeZone zone,
Locale aLocale)
{
return createCalendar(zone, aLocale);
}
就从最简单常用的无参getInstance方法进行分析。
可以看见其中调用了关键方法createCalendar,这个方法是实例化对象的关键。
public static Calendar getInstance()
{
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
Calendar整体源码还是比较清晰的,根据对源码的追溯,其中最重要的就是两点:
1.CalendarProvider对象,这个对象还是很关键的,在createCalendar的流程中进行获取到了这个对象。
2.调用CalendarProvider对象中的getInstance方法进行创建了一个Calendar对象。
private static Calendar createCalendar(TimeZone zone,
Locale aLocale)
{
CalendarProvider provider =
LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
.getCalendarProvider();
if (provider != null) {
try {
return provider.getInstance(zone, aLocale);
} catch (IllegalArgumentException iae) {
// fall back to the default instantiation
}
}
Calendar cal = null;
if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
if (cal == null) {
// If no known calendar type is explicitly specified,
// perform the traditional way to create a Calendar:
// create a BuddhistCalendar for th_TH locale,
// a JapaneseImperialCalendar for ja_JP_JP locale, or
// a GregorianCalendar for any other locales.
// NOTE: The language, country and variant strings are interned.
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
&& aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
}
这里不用去关心createCalendar是怎么获取到的,只需要关心怎么用的CalendarProvider对象,内部的原理是什么即可。
CalendarProvider其实是一个抽象类,主要是进行定义了一个关键的abstract方法getInstance,这个方法返回的是一个Calendar,主要进行用于创建Calendar实例。
/**
* An abstract class for service providers that
* provide instances of the
* {@link java.util.Calendar Calendar} class.
*
* @since 1.8
*/
public abstract class CalendarProvider extends LocaleServiceProvider {
/**
* Sole constructor. (For invocation by subclass constructors, typically
* implicit.)
*/
protected CalendarProvider() {
}
/**
* Returns a new Calendar
instance for the
* specified locale.
*
* @param zone the time zone
* @param locale the desired locale
* @exception NullPointerException if locale
is null
* @exception IllegalArgumentException if locale
isn't
* one of the locales returned from
* {@link java.util.spi.LocaleServiceProvider#getAvailableLocales()
* getAvailableLocales()}.
* @return a Calendar
instance.
* @see java.util.Calendar#getInstance(java.util.Locale)
*/
public abstract Calendar getInstance(TimeZone zone, Locale locale);
}
CalendarProviderImpl进行实现了CalendarProvider抽象类,其中最主要的是进行实现了gtetInstance方法。
会发现getInstance方法进行完成了将Calendar实例化并返回出去的任务。
/**
* Concrete implementation of the {@link sun.util.spi.CalendarProvider
* CalendarProvider} class for the JRE LocaleProviderAdapter.
*
* @author Naoto Sato
*/
public class CalendarProviderImpl extends CalendarProvider implements AvailableLanguageTags {
private final LocaleProviderAdapter.Type type;
private final Set langtags;
public CalendarProviderImpl(LocaleProviderAdapter.Type type, Set langtags) {
this.type = type;
this.langtags = langtags;
}
/**
* Returns an array of all locales for which this locale service provider
* can provide localized objects or names.
*
* @return An array of all locales for which this locale service provider
* can provide localized objects or names.
*/
@Override
public Locale[] getAvailableLocales() {
return LocaleProviderAdapter.toLocaleArray(langtags);
}
@Override
public boolean isSupportedLocale(Locale locale) {
// Support any locales.
return true;
}
/**
* Returns a new Calendar
instance for the
* specified locale.
*
* @param zone the time zone
* @param locale the desired locale
* @exception NullPointerException if locale
is null
* @exception IllegalArgumentException if locale
isn't
* one of the locales returned from
* {@link java.util.spi.LocaleServiceProvider#getAvailableLocales()
* getAvailableLocales()}.
* @return a Calendar
instance.
* @see java.util.Calendar#getInstance(java.util.Locale)
*/
@Override
public Calendar getInstance(TimeZone zone, Locale locale) {
return new Calendar.Builder()
.setLocale(locale)
.setTimeZone(zone)
.setInstant(System.currentTimeMillis())
.build();
}
@Override
public Set getAvailableLanguageTags() {
return langtags;
}
}
去理解源码中的设计思想单单靠Debug和文字讲解是比较抽象的,画出来一个UML类图进行理解会比较直观,没有这么抽象。
现在我将代码进行梳理了出来,Calendar是被依赖的类,也是进行调用创建Calendar实例对象的客户端,CalendarProviderImpl其实就是进行创建Calendar实例对象的工厂类,所以说这里进行使用的就是简单工厂设计模式。
将创建类的能力抽离到了工厂类中。
Iterator是在Collection整个体系中进行使用了这个Iterator,JDK整个Collection体系中进行使用到了Iterator,在这个集合体系中任何一个类型的集合都是一个工厂,接口和抽象类是抽象工厂,普通类是实际的生产对象的工厂,工厂进行创建的对象是Iteator。
理清几个重要的关系:
抽象工厂(定义抽象方法的接口/抽象类):接口/抽象类
实例工厂(实际执行创建实例的工厂类):实现接口/继承抽象类的实例类
实例化的对象:Iterator以及其实现类(整个体系)
将实例化的代码提取出来,放到一个类中进行统一管理和维护,达到和主项目依赖关系的解耦。从而提高项目的扩展性和维护性。
1.简单工厂模式
就是将一个抽象类型的类,其所有派生类实例化对象的创建均抽取到一个工厂类中,使用工厂类完成所有类实例化对象的创建。
实现了创建对象代码的解耦,将实例化对象的逻辑全部抽取到工厂类中。
2.工厂方法模式
就是定义一个抽象工厂类,使用这个抽象工厂派生出各种各样的工厂实现类,进行对不同种类的对象进行实例化,客户端可以根据自己的需求去选择不同的工厂实现类,调用创建自己需要的对象。
就是对简单工厂进行按不同类型对象的创建职责进行了进一步的拆分,不同类型的对象采用不同工厂进行创建,提高了扩展性和可维护性,适合大型项目进行使用。
3.抽象工厂模式
抽象工厂模式就是定义了一个抽象方法,在抽象中进行定义实例化对象的方法,工厂进行实现这个接口,实现工厂中创建哪些对象的逻辑,客户端需要进行创建对象的时候,注入这个工厂进行创建即可,需要替换的时候与,注入其它工厂即可(注入的类型为接口,方便进行替换为其它实现工厂)
其实三大工厂设计模式都是进行遵循设计模式的依赖抽象原则,比如说简单工厂中,工厂类就是一层抽象(抽象程度弱),工厂方法模式中的抽象工厂类也是一层抽象(使用抽象类进行架构设计,抽象程度强),抽象工厂设计模式也是一层抽象,使用接口进行抽象出相应的对象实例化的抽象方法,接口就是相应的抽象(抽象能力强)
创建对象实例时,不要直接new实例,而是将这个new类的动作放在一个工厂方法中,并进行返回这个对象实例。
实例化对象的类体系最好是进行抽象出来一个抽象类或者接口进行实现/继承,不要让工厂创建的对象的类型是类继承的具体的类的类型,要化具体为抽象,类型应该是抽象的,不应该是具体的。