披萨项目
披萨的项目:便于披萨种类的扩展。
1. 披萨的种类很多
2.披萨的制作流程有 prepare bake cut box
3.完成披萨店订购功能
起初的构思:
首先 客户通过向披萨店订购。然后披萨店根据类型做相对应的披萨。
类图
代码:
Pizza类
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+" cuting");
}
public void box(){
System.out.println(name+"boxing");
}
public void setName(String name){
this.name=name;
}
}
public class GreekPizza extends Pizza {
@Override
public void prepare() {
setName("GreekPizza");
System.out.println(name+" preparing");
}
}
public class CheesePizza extends Pizza{
@Override
public void prepare() {
setName("CheesePizza");
System.out.println(name+"preparing");
}
}
public class OrderPizza {
public OrderPizza(){
Pizza pizza = null;
String pizzaType;//披萨类型
do {
pizzaType = getType();
if (pizzaType.equals("greek")){
pizza = new GreekPizza();
}else if (pizza.equals("cheese")){
pizza = new CheesePizza();
}else {
break;
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}while (true);
}
private String getType(){
try {
BufferedReader strIn = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Input Pizza Type:");
String str = strIn.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
test
public class client {
public static void main(String[] args) {
new OrderPizza();
}
}
结果:
Input Pizza Type:
greek
GreekPizza preparing
GreekPizza baking
GreekPizza cuting
GreekPizzaboxing
Input Pizza Type:
end
缺点:
违背了设计原则中的OCP原则。
当你需要增加新披萨的时候,首先需要增加一个Pizza类,其次在OrderPizza中新增else if(pizzaType.equals(“xxx”)).
改进的思路:
把创建Pizza对象封装到一个类中,当有新的Pizza种类时,只需要修改该类即可,其他有创建到Pizza对象的代码就不需要修改了。【简单工厂模式】
简单工厂模式
工厂模式的一种。由一个工厂对象决定创建哪一种产品类的实例。
定义了一个创建对象的类,由这个类来封装实例化对象的行为
更改的披萨项目
类图:
代码:
public class SimpleFactory {
//根据Type创建返回相对应的Pizza
public Pizza createPizza(String pizzaType){
Pizza pizza = null;
if (pizzaType.equals("greek")){
pizza = new GreekPizza();
pizza.setName("GreekPizza");
}else if (pizzaType.equals("cheese")){
pizza = new CheesePizza();
pizza.setName("CheesePizza");
}
return pizza;
}
//静态工厂模式
public static Pizza createPizzaOfStatic(String pizzaType){
Pizza pizza = null;
if (pizzaType.equals("greek")){
pizza = new GreekPizza();
pizza.setName("GreekPizza");
}else if (pizzaType.equals("cheese")){
pizza = new CheesePizza();
pizza.setName("CheesePizza");
}
return pizza;
}
}
test
public class client {
public static void main(String[] args) {
new OrderPizza(new SimpleFactory());
}
}
【当我们会用到大量的创建某种、某类或某批对象时,就会用到工厂模式。】
工厂方法模式
在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。
工厂方法模式结构
1.产品 (Product)
将会对接口进行声明。 对于所有由创建者及其子类构建的对象, 这些接口都是通用的。
2.具体产品 (Concrete Products)
是产品接口的不同实现。
3.创建者 (Creator)
类声明返回产品对象的工厂方法。 该方法的返回对象类型必须与产品接口相匹配。
你可以将工厂方法声明为抽象方法, 强制要求每个子类以不同方式实现该方法。 或者, 你也可以在基础工厂方法中返回默认产品类型。
注意, 尽管它的名字是创建者, 但他最主要的职责并不是创建产品。 一般来说, 创建者类包含一些与产品相关的核心业务逻辑。 工厂方法将这些逻辑处理从具体产品类中分离出来。
4.具体创建者 (Concrete Creators)
将会重写基础工厂方法, 使其返回不同类型的产品。
注意, 并不一定每次调用工厂方法都会创建新的实例。 工厂方法也可以返回缓存、 对象池或其他来源的已有对象。
应用场景
1.当你在编写代码的过程中, 如果无法预知对象确切类别及其依赖关系时, 可使用工厂方法。
2. 如果你希望用户能扩展你软件库或框架的内部组件, 可使用工厂方法。
3. 如果你希望复用现有对象来节省系统资源, 而不是每次都重新创建对象, 可使用工厂方法。
实现方式
1.让所有产品都遵循同一接口。 该接口必须声明对所有产品都有意义的方法。
2.在创建类中添加一个空的工厂方法。 该方法的返回类型必须遵循通用的产品接口。
3.在创建者代码中找到对于产品构造函数的所有引用。 将它们依次替换为对于工厂方法的调用, 同时将创建产品的代码移入工厂方法。 你可能需要在工厂方法中添加临时参数来控制返回的产品类型。
工厂方法的代码看上去可能非常糟糕。 其中可能会有复杂的 switch分支运算符, 用于选择各种需要实例化的产品类。 但是不要担心, 我们很快就会修复这个问题。
4.现在, 为工厂方法中的每种产品编写一个创建者子类, 然后在子类中重写工厂方法, 并将基本方法中的相关创建代码移动到工厂方法中。
5.如果应用中的产品类型太多, 那么为每个产品创建子类并无太大必要, 这时你也可以在子类中复用基类中的控制参数。
例如, 设想你有以下一些层次结构的类。 基类 邮件及其子类 航空邮件和 陆路邮件 ; 运输及其子类 飞机, 卡车和 火车 。 航空邮件仅使用 飞机对象, 而 陆路邮件则会同时使用 卡车和 火车对象。 你可以编写一个新的子类 (例如 火车邮件 ) 来处理这两种情况, 但是还有其他可选的方案。 客户端代码可以给 陆路邮件类传递一个参数, 用于控制其希望获得的产品。
6.如果代码经过上述移动后, 基础工厂方法中已经没有任何代码, 你可以将其转变为抽象类。 如果基础工厂方法中还有其他语句, 你可以将其设置为该方法的默认行为。
工厂方法模式优缺点
优点:
✔️ 你可以避免创建者和具体产品之间的紧密耦合。
✔️ 单一职责原则。 你可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护。
✔️ 开闭原则。 无需更改现有客户端代码, 你就可以在程序中引入新的产品类型。
缺点:
❌应用工厂方法模式需要引入许多新的子类, 代码可能会因此变得更复杂。 最好的情况是将该模式引入创建者类的现有层次结构中。
披萨项目新的需求
客户在点披萨时,可以点不同口味的披萨。
eg:北京的奶酪pizza、伦敦的胡椒pizza等等。【地点、口味多个维度】
思路:将实例化功能抽象成抽象方法,在不同口味点餐子类中具体实现。
工厂方法:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
类图
代码:
public abstract class OrderPizza {
abstract Pizza createPizza(String type);
public OrderPizza(){
Pizza pizza = null;
String type;
do {
type = getType();
pizza = createPizza(type); //创建pizza由工厂子类完成。
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}while (true);
}
private String getType(){
try {
BufferedReader strIn = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Input Pizza Type:");
String str = strIn.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
两个OrderPizza
public class BJOrderPizza extends OrderPizza{
@Override
Pizza createPizza(String type) {
Pizza pizza =null;
if (type.equals("cheese")){
pizza=new BJCheesePizza();
}else if (type.equals("greek")){
pizza=new BJGreekPizza();
}
return pizza;
}
}
public class LDOrderPizza extends OrderPizza {
@Override
Pizza createPizza(String type) {
Pizza pizza =null;
if (type.equals("cheese")){
pizza=new LDCheesePizza();
}else if (type.equals("greek")){
pizza=new LDGreekPizza();
}
return pizza;
}
}
四个Pizza
public class LDCheesePizza extends Pizza {
@Override
public void prepare() {
setName("LDCheesePizza");
System.out.println(name+"preparing");
}
}
public class LDGreekPizza extends Pizza{
@Override
public void prepare() {
setName("LDGreekPizza");
System.out.println(name+"preparing");
}
}
public class BJGreekPizza extends Pizza{
@Override
public void prepare() {
setName("BJGreekPizza");
System.out.println(name+"preparing");
}
}
public class BJCheesePizza extends Pizza {
@Override
public void prepare() {
setName("BJCheesePizza");
System.out.println(name+"preparing");
}
}
test
public class client {
public static void main(String[] args) {
new LDOrderPizza();
}
}
抽象工厂模式
定义一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类。
可以将简单工厂和工厂方法进行整合。
将工厂抽象成两层,AbsFactory和具体实现的子类工厂。程序员可以根据创建对象类型使用对应的工厂子类。这样可以将单个的简单工程类变成工厂簇,更利于代码维护和扩展。
创建对象实例时,不要直接 new 类 , 而是把这个 new 类的动作放在一个工厂的方法中,并返回。有的书上说,变量不要直接持有具体类的引用。
不要让类继承具体类,而是继承抽象类或者是 实 现 interface( 接口)
不要覆盖基类中已经实现的方法。
抽象工厂模式结构
1.抽象产品 (Abstract Product)
为构成系列产品的一组不同但相关的产品声明接口。
2.具体产品 (Concrete Product)
是抽象产品的多种不同类型实现。 所有变体 (维多利亚/现代) 都必须实现相应的抽象产品 (椅子/沙发)。
3.抽象工厂 (Abstract Factory)
接口声明了一组创建各种抽象产品的方法。
4.具体工厂 (Concrete Factory)
实现抽象工厂的构建方法。 每个具体工厂都对应特定产品变体, 且仅创建此种产品变体。
5.尽管具体工厂会对具体产品进行初始化, 其构建方法签名必须返回相应的抽象产品。 这样, 使用工厂类的客户端代码就不会与工厂创建的特定产品变体耦合。客户端 (Client)
只需通过抽象接口调用工厂和产品对象, 就能与任何具体工厂/产品变体交互。
应用场景
1.如果代码需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体类进行构建, 在这种情况下, 你可以使用抽象工厂。
2.如果你有一个基于一组抽象方法的类, 且其主要功能因此变得不明确, 那么在这种情况下可以考虑使用抽象工厂模式。
实现方式
1.以不同的产品类型与产品变体为维度绘制矩阵。
2.为所有产品声明抽象产品接口。 然后让所有具体产品类实现这些接口。
3.声明抽象工厂接口, 并且在接口中为所有抽象产品提供一组构建方法。
4.为每种产品变体实现一个具体工厂类。
5.在应用程序中开发初始化代码。 该代码根据应用程序配置或当前环境, 对特定具体工厂类进行初始化。 然后将该工厂对象传递给所有需要创建产品的类。
6.找出代码中所有对产品构造函数的直接调用, 将其替换为对工厂对象中相应构建方法的调用。
抽象工厂模式优缺点
优点:
✔️ 你可以确保同一工厂生成的产品相互匹配。
✔️你可以避免客户端和具体产品代码的耦合。
✔️单一职责原则。 你可以将产品生成代码抽取到同一位置, 使得代码易于维护。
✔️ 开闭原则。 向应用程序中引入新产品变体时, 你无需修改客户端代码。
缺点:
❌ 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂。
改进的项目
类图
代码:
抽象工厂类
public interface AbsFactory {
public Pizza createPizaa(String type);
}
public class BJOrderPizza implements AbsFactory{
@Override
public Pizza createPizaa(String type) {
Pizza pizza = null;
if (type.equals("cheese")){
pizza = new BJCheesePizza();
}else if (type.equals("greek")){
pizza = new BJGreekPizza();
}
return pizza;
}
}
public class LDOrderPizza implements AbsFactory{
@Override
public Pizza createPizaa(String type) {
Pizza pizza = null;
if (type.equals("cheese")){
pizza = new LDCheesePizza();
}else if (type.equals("greek")){
pizza = new LDGreekPizza();
}
return pizza;
}
}
public class OrderPizza {
AbsFactory absFactory;
public OrderPizza(AbsFactory factory){
setFactory(factory);
}
private void setFactory(AbsFactory absFactory){
Pizza pizza = null;
String type = "" ;
this.absFactory = absFactory;
do {
type = getType();
pizza = absFactory.createPizaa(type);
if (pizza != null){
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}else {
System.out.println("未有您需要的Pizza");
break;
}
}while (true);
}
private String getType(){
try {
BufferedReader strIn = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Input Pizza Type:");
String str = strIn.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
test
public class client {
public static void main(String[] args) {
new OrderPizza(new LDOrderPizza());
}
}