工厂方法模式
概述
工厂方法模式,父类决定实例的生成方式,但不决定所要生成的具体的类,具体的处理全部交给子类负责,这样就可以将生成实例的框架(framework)和实际负责生成实例的类解耦。
示例程序
示例程序的作用是制作身份证,他其中有4个类
- Product类和Factory类属于framework包,这两个类组成了生成实例的框架
- IDCard类和IDCardFactory类负责实际的加工处理,他么数据idcard包
包 | 名字 | 说明 |
---|---|---|
framework | Product | 只定义抽象方法use的抽象类 |
framework | Factory | 实现了create方法的抽象类 |
idcard | IDCard | 实现了use方法的类 |
idcard | IDCardFacoty | 实现了createProduct、registerProduct的方法的类 |
Product类
public abstract class Product {
public abstract void use();
}
Factory类
Factory类中,声明了用于生成产品的createProduct抽象方法和注册产品的registerProduct抽象方法。生成产品和注册产品的具体处理交给子类来实现。
在这个框架中,我们定义了工厂是用来调用create方法来生成Product实例的,而create方法的实现是先调用createProduct生成产品,在调用registerProduct来注册产品
public abstract class Factory {
public final Product create(String owner){
Product product = createProduct(owner);
registerProduct(product);
return product;
}
public abstract Product createProduct(String owner);
public abstract void registerProduct(Product product);
}
IDCard类
@Slf4j
@Data
public class IDCard extends Product {
private String owner;
IDCard(String owner) {
log.info("制作{}的身份证",owner);
this.owner = owner;
}
@Override
public void use() {
log.info("使用{}的身份证",owner);
}
}
IDCardFactory类
IDCardFactory类实现了createProduct方法和registerProduct方法
createProduct方法通过生成IDCard的实例来生产产品
registerProduct方法通过将IDCard的持有人(owner)保存到owners字段中来实现注册产品
public class IDCardFactory extends Factory {
private List<String> owners = new ArrayList<>();
@Override
public Product createProduct(String owner) {
return new IDCard(owner);
}
@Override
public void registerProduct(Product product) {
IDCard idCard = (IDCard) product;
owners.add(idCard.getOwner());
}
public List getOwners(){
return owners;
}
}
测试
@SpringBootTest
class Practice403ApplicationTests {
@Test
void contextLoads() {
Factory factory = new IDCardFactory();
Product card01 = factory.create("张三");
Product card02 = factory.create("李四");
Product card03 = factory.create("王五");
card01.use();
card02.use();
card03.use();
}
}
//结果如下
//制作张三的身份证
//制作李四的身份证
//制作王五的身份证
//使用张三的身份证
//使用李四的身份证
//使用王五的身份证
工厂方法模式登场角色
-
Product(产品)
Product角色属于框架的一方,是一个抽象类,他定义了工厂方法模式中生成的那些实例所持有的接口,但具体的处理由子类ConcreteProduct来实现,在示例程序中由Product类扮演这一角色
-
Creator(创建者)
Creator角色属于框架的一方,他是负责生成Product角色的抽象类,但具体的处理由子类ConcreteCreator角色决定,在示例程序中由Factory来扮演这一角色
Creator角色对于ConcreteCreator角色一无所知,他唯一知道的是只要调用Product角色和生成实例的方法(factoryMethod)就可以生成Product实例。在示例程序中,CreateProduct方法是生成实例的方法。不用new关键字来生成实例,而是调用生成实例的专用方法来生成实例,这一就可以防止父类与其他具体类耦合。
-
ConcreteProduct(具体的产品)
ConcreteProduct角色属于具体实现的一方,他决定了具体的产品,在示例程序中由IDCard来扮演这一角色
-
ConcreteCreator(具体的创建者)
ConcreteCreator角色属于具体实现的一方,他负责生成具体的产品,在示例程序中由IDCardFactory来扮演着一角色
练习题
习题1
**问:**在示例程序中,IDCard类的构造函数并不是public,请问这是想表达什么意思?
**答:**这是为了保证除IDCard以外的类,不能通过new的方法来创建IDCard实例,强制他们通过IDCardFactory来创建IDCard实例
@Slf4j
@Data
public class IDCard extends Product {
private String owner;
IDCard(String owner) {
log.info("制作{}的身份证",owner);
this.owner = owner;
}
@Override
public void use() {
log.info("使用{}的身份证",owner);
}
}
习题2
修改实例程序,为IDCard类添加卡的编号,并在IDCardFactory类中保存编号与所有者之间的对应表
要实现上述需求,在我们的实例程序中不必修改框架中的内容,只需要修改IDCard以及IDCardFactory即可
@Slf4j
@Data
public class IDCard extends Product {
private String owner;
private Integer cardNumber;
IDCard(String owner,Integer cardNumber) {
log.info("制作{}的身份证,卡号为{}",owner,cardNumber);
this.owner = owner;
this.cardNumber = cardNumber;
}
@Override
public void use() {
log.info("使用{}的身份证",owner);
}
}
public class IDCardFactory extends Factory {
private HashMap<Integer,String> dataBase = new HashMap<>();
int cardNumber = 0;
@Override
public Product createProduct(String owner) {
return new IDCard(owner,cardNumber++);
}
@Override
public void registerProduct(Product product) {
IDCard idCard = (IDCard) product;
dataBase.put(idCard.getCardNumber(),idCard.getOwner());
}
public HashMap<Integer,String> getOwners(){
return dataBase;
}
}
//测试
@Test
void test01(){
IDCardFactory factory = new IDCardFactory();
Product card01 = factory.create("张三");
Product card02 = factory.create("李四");
Product card03 = factory.create("王五");
card01.use();
card02.use();
card03.use();
HashMap<Integer,String> owners = factory.getOwners();
for (Map.Entry<Integer,String> entry : owners.entrySet()){
log.info("{}:{}",entry.getKey(),entry.getValue());
}
}
//结果
//0:张三
//1:李四
//2:王五
习题3
**问:**为了强制调用方向Product类的子类的构造函数中传入“产品名称”作为参数,我们采用如下的定义方式,但是在编译代码时却出现了编译错误,这是为什么?
**答:**在Java中无法定义抽象类的构造函数,构造函数是不会被挤成的,因此定义抽象的构造函数没有任何意义。要想实现习题中的要求,应当另外声明一个设置产品名称的专用方法
//错误示例
public abstract class Product {
public abstract Product(String name);
public abstract void use();
}