1. 传统方式
需求演示:
看一个披萨的项目:要便于披萨种类的扩展,要便于维护,完成披萨订购功能。
传统方式实现如下:
- 1.首先定义一个抽象类Pizza,类中有四个方法:准备原材料prepare、切割cut、烘烤bake、装盒box。每种披萨的制作原材料不同,所以先写成抽象方法。
abstract class Pizza {
var name:String = _
// 假定,每种pizza的准备材料不同,因此做成抽象的...
def prepare()
def cut(): Unit = {
println(this.name + "cutting...")
}
def bake(): Unit = {
println(this.name + "baking...")
}
def box(): Unit = {
println(this.name + "boxing...")
}
}
- 2.然后定义俩个类继承 Pizza 类,这俩个类分别是:胡椒披萨类和希腊披萨类
class PepperPizza extends Pizza {
override def prepare(): Unit = {
this.name = "胡椒pizza"
println(this.name + " preparing")
}
}
class GreekPizza extends Pizza{
override def prepare(): Unit = {
this.name = "希腊pizza"
println(this.name + " preparing...")
}
}
- 3.创建一个制作披萨的类OrderPizza,判断客户想要的是什么披萨,然后进行制作。
import scala.io.StdIn
import scala.util.control.Breaks._
import cn.ScalaDemos.设计模式.简单工厂模式.pizza.{GreekPizza, PepperPizza, Pizza}
class OrderPizza {
var orderType:String = _
var pizza:Pizza = _
breakable {
do {
println("请输入pizza的类型")
orderType = StdIn.readLine()
if (orderType.equals("greek")) {
this.pizza = new GreekPizza
} else if (orderType.equals("pepper")) {
this.pizza = new PepperPizza
} else {
break()
}
this.pizza.prepare()
this.pizza.bake()
this.pizza.cut()
this.pizza.box()
} while (true)
}
}
- 4.主程序调用OrderPizza类。
object PizzaStore {
def main(args: Array[String]): Unit = {
val orderPizza = new OrderPizza
println("退出程序")
}
}
2. 简单工厂模式对比改进
基本介绍
- 简单工厂模式属于创建型模式,但是不属于23种GOF设计模式之一,简单工厂是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式。
- 简单工厂模式:定义一个创建对象的类,由这个类来封装实例化对象的行为,这满足创建型模式中所要求的“创建与使用相分离”的特点。
- 在简单工厂模式中创建实例的方法通常为静态方法,因此简单工厂模式又叫做静态工厂方法模式。
- 在软件开发中,当我们会用大量的创建某种、某类或者谋批对象时,就会使用到工厂模式。
使用简单工厂模式进行改进
- 传统的方式比较好理解,简单易操作。
- 但是违反了设计模式的 ocp原则,即对扩展开放,对修改关闭。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码。
- 如果,我们要增加一种之前没有的披萨,在传统的模式下,就得在制作披萨的类中(OrderPizza)增加一个种类。但是OrderPizza类可以有多个,换种方式理解,在实际情况中,可能有很多个店面都能做披萨,那么按照传统的方式,就得修改很多个类,修改很多代码。
传统方式类图:
改进思路分析:
- 修改代码可以,但是如果我们在其它地方也有创建Pizza的代码,就意味着,也需要修改,而创建Pizza的代码,往往有很多处。
- 把创建Pizza对象封装到一个类中,这样我们有新的Pizza种类时,只需要修改该类就可以,其它有创建到Pizza对象的代码就不需要修改了。–> 简单工厂模式。
简单工厂模式设计类图:
代码如下:
- 设计一个工厂类,提供一个创建pizza的方法,有需要创建pizza时,直接调用该方法即可。
import cn.ScalaDemos.设计模式.简单工厂模式.pizza.{CheesePizza, GreekPizza, PepperPizza, Pizza}
object SimleFactory {
// 提供一个创建pizza的方法,有需要创建pizza时,调用该方法即可
def createPizza(t: String): Pizza = {
var pizza: Pizza = null
if (t.equals("greek")) {
pizza = new GreekPizza
} else if (t.equals("pepper")) {
pizza = new PepperPizza
} else if (t.equals("cheese")) {
pizza = new CheesePizza
}
pizza
}
}
- 制作披萨类(OrderPizza):只需调用工厂类中的方法即可!!!
import scala.io.StdIn
import scala.util.control.Breaks._
import cn.ScalaDemos.设计模式.简单工厂模式.pizza.{GreekPizza, PepperPizza, Pizza}
class OrderPizza {
var orderType:String = _
var pizza:Pizza = _
breakable {
do {
println("请输入pizza的类型")
orderType = StdIn.readLine()
pizza = SimleFactory.createPizza(orderType)
if (pizza == null) {
break()
}
this.pizza.prepare()
this.pizza.bake()
this.pizza.cut()
this.pizza.box()
} while (true)
}
}
总结
简单工厂设计模式的优缺点:
- 优点:
- 工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。
- 客户端无需知道所创建具体产品的类名,只需知道参数即可。
- 也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。
- 缺点:
- 简单工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则。
- 使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度。
- 系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂。
- 简单工厂模式使用了 static 工厂方法,造成工厂角色无法形成基于继承的等级结构。
补充说明,为什么简单工厂模式也叫静态工厂模式???我们的工厂类中创建产品实例的那个方法可以直接设置成static静态方法,这样写的好处是可以让代码看起来比较简单,因为静态方法的特点是类加载的时候会默认执行该方法,所以说不需要我们设置初始化方法来调用。
3. 工厂方法模式对比改进
针对上述案例需求,看一个新的需求:客户在点披萨时,可以点不同口味的披萨,比如北京的,伦敦的等等,那么如何改进呢???
- 思路1:
- 使用简单工厂模式,创建不同的简单工厂类,比如BJPizzaSimpleFactory、LDPizzaSimpleFactory 等等。从当前这个案例来说,也是可以的,但是考虑到规模问题,以及软件的可维护性、可扩展性,并不是很好的改进方式。
- 思路2:
- 使用工厂方法模式
工厂方法模式介绍:
- 工厂方法模式设计方案:将披萨项目的实例化功能抽象成抽象方法,在不同的口味点餐子类当中具体实现。
- 工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
代码演示:如下图
- 将制作披萨的类(OrderPizza)抽象化
import cn.ScalaDemos.设计模式.工厂方法模式.pizza.Pizza
import scala.io.StdIn
import scala.util.control.Breaks._
abstract class OrderPizza {
var orderType:String = _
var pizza:Pizza = _
breakable {
do {
println("请输入pizza的类型")
orderType = StdIn.readLine()
pizza = createPizza(orderType)
if (pizza == null) {
break()
}
this.pizza.prepare()
this.pizza.bake()
this.pizza.cut()
this.pizza.box()
} while (true)
}
// 抽象方法:createPizza(orderType),让各个子类实现
def createPizza(t: String): Pizza
}
- 创建 BJ、LD 俩个子类继承 OrderPizza
import cn.ScalaDemos.设计模式.工厂方法模式.pizza.{BJCheesePizza, BJPepperPizza, Pizza}
class BJOrderPizza extends OrderPizza {
override def createPizza(t: String): Pizza = {
println("使用工厂方法模式")
var pizza: Pizza = null
if (t.equals("cheese")) {
pizza = new BJCheesePizza
} else if (t.equals("pepper")) {
pizza = new BJPepperPizza
}
pizza
}
}
import cn.ScalaDemos.设计模式.工厂方法模式.pizza.{BJCheesePizza, BJPepperPizza, Pizza}
class LDOrderPizza extends OrderPizza {
override def createPizza(t: String): Pizza = {
println("使用工厂方法模式")
var pizza: Pizza = null
if (t.equals("cheese")) {
pizza = new BJCheesePizza
} else if (t.equals("pepper")) {
pizza = new BJPepperPizza
}
pizza
}
}
- 然后分别创建北京的披萨类、伦敦的披萨类进行制作
class BJPepperPizza extends Pizza {
override def prepare(): Unit = {
this.name = "北京胡椒pizza"
println(this.name + " preparing...")
}
}
class BJCheesePizza extends Pizza {
override def prepare(): Unit = {
this.name = "北京奶酪pizza"
println(this.name + " preparing...")
}
}
class LDPepperPizza extends Pizza {
override def prepare(): Unit = {
this.name = "伦敦胡椒pizza"
println(this.name + " preparing...")
}
}
class LDCheesePizza extends Pizza {
override def prepare(): Unit = {
this.name = "伦敦奶酪pizza"
println(this.name + " preparing...")
}
}
类图如下:
总结
简单工厂模式违背了开闭原则,而“工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。
- 优点:
- 用户只需要知道具体工厂名称就可以得到所要的产品,无需知道产品的具体创建过程。
- 灵活性强,对于产品的创建,只需要多写一个相应的工厂类。
- 典型的解耦框架。高层模块只需要知道产品的抽象类,无需关心其它实现类,满足迪米特法则、依赖倒置原则和里氏转缓原则。
- 缺点:
- 类的个数容易过多,增加复杂度。
- 增加了系统的抽象性和理解难度。
- 抽象产品只能生产一种产品,此弊端可使用抽象工厂模式解决。
4. 抽象工厂模式
基本介绍
- 抽象工厂模式:定义了一个 trait (Java中的接口)用于创建相关或有依赖关系的对象簇,而无需指明具体的类
- 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合
- 从设计层面上看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)
- 将工厂抽象成两层,AbsFactory(抽象工厂)和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
类图如下:
代码实现:
- 首先定义一个接口,并且创建一个抽象方法,一个抽象工厂
import cn.ScalaDemos.设计模式.抽象工厂模式.pizza.Pizza
trait AbsFactory {
// 定义一个抽象方法
def createPizza(t: String): Pizza
}
- 然后定义实现类实现接口,注意在子工厂当中进行披萨制作,比如:是北京披萨就在北京工厂当中制作,是伦敦披萨就在伦敦披萨中制作。
import cn.ScalaDemos.设计模式.抽象工厂模式.pizza.{BJCheesePizza, BJPepperPizza, Pizza}
// 这是一个实现了AbsFactory的一个实现类
// 如果我们希望订购北京的披萨,就使用该实现类即可
class BJFactory extends AbsFactory {
override def createPizza(t: String): Pizza = {
var pizza: Pizza = null
if (t.equals("cheese")) {
pizza = new BJCheesePizza
} else if (t.equals("pepper")) {
pizza = new BJPepperPizza
}
pizza
}
}
import cn.ScalaDemos.设计模式.抽象工厂模式.pizza.{LDCheesePizza, LDPepperPizza, Pizza}
class LDFactory extends AbsFactory {
override def createPizza(t: String): Pizza = {
var pizza: Pizza = null
if (t.equals("cheese")) {
pizza = new LDCheesePizza
} else if (t.equals("pepper")) {
pizza = new LDPepperPizza
}
pizza
}
}
- 然后在制作披萨类(OrderPizza)当中,将工厂直接传入到该类中,因为工厂中已经知道需求,北京工厂只能制作北京披萨,伦敦工厂只能制作伦敦披萨,在这个类中要传入工厂,所以使用多态语法。
import cn.ScalaDemos.设计模式.抽象工厂模式.pizza.Pizza
import scala.io.StdIn
import scala.util.control.Breaks._
// OrderPizza,当我们使用抽象工厂模式后,我们订购一个Pizza思路
// 接收一个子工厂的实例,根据该工厂的一个创建要求去实例化
class OrderPizza {
var absFactory: AbsFactory = _
def this(absFactory: AbsFactory) { //多态用法
this
breakable {
var orderType: String = null
var pizza: Pizza = null
do {
println("请输入pizza的类型,使用抽象工厂模式...")
orderType = StdIn.readLine()
// 使用简单工厂模式来创建对象
pizza = absFactory.createPizza(orderType)
if (pizza == null ) {
break()
}
pizza.prepare()
pizza.bake()
pizza.cut()
pizza.box()
} while (true)
}
}
}
- 主方法调用,因为调用制作披萨类时需要传入一个工厂对象,所以new一个即可
object PizzaStore {
def main(args: Array[String]): Unit = {
new OrderPizza(new BJFactory)
// new OrderPizza(new LDFactory)
println("退出程序")
}
}
总结
- 抽象工厂模式除了有工厂方法模式的优点外,还有以下几点:
- 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
- 当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品组。
- 抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则。
- 缺点:
- 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。增加了系统的抽象性和理解难度。
5. 工厂模式总结
工厂模式 | 对比理解 |
---|---|
简单工厂模式 | 定义一个创建对象的类,由这个类来封装实例化对象的行为,这满足创建型模式中所要求的“创建与使用相分离”的特点。 |
工厂方法模式 | 定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。 |
抽象工厂模式 | 定义了一个接口用于创建相关或有依赖关系的对象簇,而无需指明具体的类。 |
- 工厂模式的意义
- 将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
- 设计模式的依赖抽象原则
- 创建对象实例时,不要直接 new 类,而是把这个new类的动作放在一个工厂的方法中,并返回。有的书上说,变量不要直接持有具体类的引用。
- 不要让类继承具体类,而是继承抽象类或者是实现接口
- 不要覆盖基类中已经实现的方法。