学习工厂设计模式,必须知道的相关概念:
学习工厂设计模式,必须知道的相关概念:
1.产品:
类
2.抽象产品:
抽象类、接口
3.产品簇
多个有内在联系,或者是有逻辑关系的产品。
4.产品等级
不同产品,如可乐,冰封,都是饮料,但是是不同产品
简单工厂
把代码分为服务端和客户端代码
//客户端
public class AppTest {
public static void main(String[] args) {
Food food = new Hamburger();
food.eat();
}
}
//==========================================
//服务端
interface Food{
void eat();
}
class Hamburger implements Food{
//如果修改为Class Hamburger3 那么客户端new Hamburger3();违反了接口隔离和迪米特法则
@Override
public void eat() {
System.out.println("吃汉堡");
}
}
/**
* 这种设计相当脆弱!为什么呢? 因为,只要作者修改了具体产品的类名,那么客户端代码,也要随之一起改变。
* 这样服务器端代码,和客户端代码就是耦合的!*/
我们尝试用简单工厂来解决
模式的结构
工厂角色(Creator):这是简单工厂模式的核心,由它负责创建所有的类的内部逻辑。当然工厂类必须能够被外界调用,创建所需要的产品对象。
抽象(Product)产品角色:简单工厂模式所创建的所有对象的父类,注意,这里的父类可以是接口也可以是抽象类,它负责描述所有实例所共有的公共接口。
具体产品(Concrete Product)角色:简单工厂所创建的具体实例对象,这些具体的产品往往都拥有共同的父类。
//客户端
public class AppTest2 {
public static void main(String[] args) {
FoodFactory.getFood(2).eat();
FoodFactory.getFood(1).eat();
}
}
//==========================================
//服务端
//抽象
interface Food2{
void eat();
}
//简单工厂
class FoodFactory{
public static Food2 getFood(int i){
Food2 food2 = null;
switch (i){
case 1:
food2 = new Hamburger2();
break;
case 2:
food2 = new RiceNoodle();
break;
}
return food2;
}
}
//具体产品
//米线
class RiceNoodle implements Food2{
@Override
public void eat() {
System.out.println("吃米线");
}
}
//汉堡
class Hamburger2 implements Food2{
@Override
public void eat() {
System.out.println("吃汉堡");
}
优点
1.把具体产品的类型,从客户端代码中,解耦出来。
2.服务器端,如果修改了具体产品的类名,客户端也知道!
这便符合了“面向接口编程的思想
缺点
1.客户端不得不死记硬背那些常量与具体产品的映射关系,比如: 1对应汉堡包,2 对应米线
2.如何具体产品特别多, 则简单工厂,就会变得十分臃肿。比如有100个具体产品,则需要在简单工厂的switch中写出100个case! .
3.最重要的是,变化来了:客户端需要扩展具体产品的时候,势必要修改简单工厂中的代码,这样便违反了“开闭原则”。
工厂方法
模式的结构
工厂方法模式的主要角色如下。
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应
//客户端
class AppTest {
public static void main(String[] args) {
Factory hamburgerFactory = new HamburgerFactory();
hamburgerFactory.getFood().eat();
Factory riceNoodleFactory = new RiceNoodleFactory();
riceNoodleFactory.getFood().eat();
Business.taste(new HamburgerFactory());
}
}
//==========================================
//服务端
class Business{
public static void taste(Factory factory){
System.out.println("评委1,准备品尝");
factory.getFood().eat();
System.out.println("评委2,准备品尝");
factory.getFood().eat();
System.out.println("评委3,准备品尝");
factory.getFood().eat();
}
}
interface Factory{
public Food getFood();
}
class HamburgerFactory implements Factory{
@Override
public Food getFood() {
return new Hamburger();
}
}
class RiceNoodleFactory implements Factory{
@Override
public Food getFood() {
return new RiceNoodle();
}
}
interface Food{
void eat();
}
//米线
class RiceNoodle implements Food{
@Override
public void eat() {
System.out.println("吃米线");
}
}
//汉堡
class Hamburger implements Food{
@Override
public void eat() {
System.out.println("吃汉堡");
}
}
优点
1.仍然具有简单工厂的优点:服务器端修改了具体产品的类名以后,客户端不知道!
2.当客户端需要扩展一个新的产品时,不需要修改作者原来的代码,只是扩展一个新的工厂而已!
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
- 灵活性增强,对于新产品的创建,只需多写一个相应的工厂类。
- 典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。
杠点
1.我们已经知道, 简单工厂也好,厂方法也好,都有一个优点,就是服务器端的具体产品类名变化了以后,客户端不知道!
但是,反观我们现在的代码,客户端仍然依赖于具体的工厂的类名呀!此时,如果服务器端修改了具体工厂的类名,那么客户端也要随之一起修改! 感觉折腾了一圈,又回到了原点! ! !
解释:
工厂的名字,是为视为接口的。作者有责任,有义务,保证工厂的名字是稳定的。也就是说,虽然客户端依赖于工厂的具体类名,可是在IT业内,所有工厂的名字都是趋向于稳定(并不是100%不会变)。至少工厂类的名字,要比具体产品类的名字更加稳定!
2.既然产品是我们自己客户端扩展出来的,那为什么不直接自己实例化呢?毕竟这个扩展出来的Lp这个产品,我们自己就是作者。我们想怎么改类名自己都能把控!为什么还要为自己制作的产品做工厂呢?
解释:
因为,作者在开发功能时,不仅仅只会开发一些抽象产品、具体产品、对应的工厂,还会配套地搭配-些提前做好的框架。
**3.**现在制作出LpFactory, 是为了能把LpFactory传入给Bussiness. taste方法,所以,必须定义这个LpFactory.那为什么不从一开始,
就让Bussiness . taste方法就直接接受Food参数呢?而不是现在的FoodFactory作为参数。
解释:
传参数Food,是因为Food类名不如工厂稳定,另外一个就是,传参都是一个Food,并不会产生新对象。
缺点
- 类的个数容易过多,增加复杂度
- 增加了系统的抽象性和理解难度
- 抽象产品只能生产一种产品,此弊端可使用抽象工厂模式解决。
抽象工厂
模式的结构
抽象工厂模式的主要角色如下。
- 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。
//客户端
class AppTest {
public static void main(String[] args) {
Business.taste(new KFCFactory());
}
}
//==========================================
//服务端
class Business{
public static void taste(Factory factory){
System.out.println("评委1,准备品尝");
factory.getFood().eat();
factory.getDrink().drink();
System.out.println("评委2,准备品尝");
factory.getFood().eat();
factory.getDrink().drink();
System.out.println("评委3,准备品尝");
factory.getFood().eat();
factory.getDrink().drink();
}
}
interface Factory{
public Food getFood();
public Drink getDrink();
}
class KFCFactory implements Factory{
@Override
public Food getFood() {
return new Hamburger();
}
@Override
public Drink getDrink() {
return new Cola();
}
}
class SanQinFactory implements Factory{
@Override
public Food getFood() {
return new RiceNoodle();
}
@Override
public Drink getDrink() {
return new Ice();
}
}
interface Food{
void eat();
}
interface Drink{
void drink();
}
//米线
class RiceNoodle implements Food{
@Override
public void eat() {
System.out.println("吃米线");
}
}
//汉堡
class Hamburger implements Food{
@Override
public void eat() {
System.out.println("吃汉堡");
}
}
class Cola implements Drink{
@Override
public void drink() {
System.out.println("喝可乐");
}
}
class Ice implements Drink{
@Override
public void drink() {
System.out.println("喝冰峰");
}
}
抽象工厂的
优点
1.仍然有简单工厂和工厂方法的优点
2.更重要的是,抽象工厂把工厂类的数量减少了! 无论有多少个产品等级,工厂就一套
使用抽象工厂模式一般要满足以下条件。
- 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
- 系统一次只可能消费其中某一族产品,即同族的产品一起使用。
杠点
1.为什么三秦工厂中,就必须是米线搭配冰峰呢?为什么就不能是米线搭配可乐?
解释:
抽象工厂中,可以生产多个产品,这多个产品之间,必须有内在联系。
同一个工厂中的产品都属于同-一个产品簇! !不能把不同产品簇中的产品混合到-一个抽象工厂的实现类中。
缺点
1.当产品等级发生变化时(增加产品等级、删除产品等级),都要引起所有以前工厂代码的修改,这就违反了“开闭原则”!
结论
当产品等级比较固定时,可以考虑使用抽象工厂,
如果产品等价经常变化,则不建议使用抽象工厂。
产品不扩充,简单工厂最好,产品等级只有一个,工厂方法最好。