何时使用抽象工厂模式?

5

我想知道什么时候需要使用抽象工厂模式。

这里有一个例子,我想知道是否必要使用它。

这是UML图

上面是抽象工厂模式,它被我的同学推荐。下面是我自己的实现。我不认为使用这个模式是必要的。

以下是一些核心代码:

    package net;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;



public class Test {
    public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
        DaoRepository dr=new DaoRepository();
        AbstractDao dao=dr.findDao("sql");
        dao.insert();
    }
}

class DaoRepository {
    Map<String, AbstractDao> daoMap=new HashMap<String, AbstractDao>();
    public DaoRepository () throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException  {
        Properties p=new Properties();
        p.load(DaoRepository.class.getResourceAsStream("Test.properties"));
        initDaos(p);
    }
    public void initDaos(Properties p) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        String[] daoarray=p.getProperty("dao").split(",");
        for(String dao:daoarray) {
            AbstractDao ad=(AbstractDao)Class.forName(dao).newInstance();
            daoMap.put(ad.getID(),ad);
        }
    }
    public AbstractDao findDao(String id) {return daoMap.get(id);}

}
abstract class AbstractDao {
    public abstract String getID();
    public abstract void insert();
    public abstract void update();
}
class SqlDao extends AbstractDao {
    public SqlDao() {}
    public String getID() {return "sql";}
    public void insert() {System.out.println("sql insert");}
    public void update() {System.out.println("sql update");}
}
class AccessDao extends AbstractDao {
    public AccessDao() {}
    public String getID() {return "access";}
    public void insert() {System.out.println("access insert");}
    public void update() {System.out.println("access update");}
}

Test.properties文件的内容只有一行:

dao=net.SqlDao,net.SqlDao

请问这种情况是必要的吗?


-------------------下面是添加的内容,解释实际情况--------------

我使用DAO的例子是因为它很普遍,任何人都知道。

实际上,我现在所做的与DAO无关,我正在构建一个Web服务,该Web服务包含一些算法来将文件转换为其他格式,例如net.CreatePDF、net.CreateWord等,它向客户端公开两个接口:getAlgorithms和doProcess。

getAlogrithoms将返回所有算法的ID,每个ID都与相应的算法相关联。

调用doProcess方法的用户还将提供他想要的算法ID。

所有算法都扩展了AbstractAlgorithm,其中定义了run()方法。

我使用AlgorithmRepository来存储所有算法(从属性文件中配置算法的具体Java类的Web服务管理器中)。也就是说,Web服务公开的DoProcess接口由具体的算法执行。

我可以举一个简单的例子: 1)用户发送getAlgorithms请求:

http://host:port/ws?request=getAlgorithms

然后用户将会获得一个包含在xml中的算法列表。
<AlgorithmsList>
  <algorithm>pdf</algorithm>
  <algorithm>word<algorithm>
</AlgorithmsList>

2) 用户通过发送DoProcess到服务器来执行操作:

http://xxx/ws?request=doProcess&alogrithm=pdf&file=http://xx/Test.word

当服务器收到这种类型的请求时,它将根据AlgorithmRepostory中的“algorithm”参数(在此请求中为pdf)获取具体的算法实例。然后调用该方法:

AbstractAlgorithm algo=AlgorithmRepostory.getAlgo("pdf");
algo.start();

然后会向用户发送一个pdf文件。

顺便提一下,在这个例子中,每个算法类似于sqlDao、AccessDao。以下是图片:

设计图片

现在,AlgorithmRepostory 需要使用抽象工厂吗?

3个回答

2
两种方法的主要区别在于顶部方法使用不同的DAO工厂创建DAO,而底部方法存储一组DAO并返回对存储库中DAO的引用。
如果多个线程需要同时访问相同类型的DAO,则底部方法存在问题,因为JDBC连接未进行同步。
可以通过使DAO实现一个newInstance()方法来解决这个问题,该方法只需创建并返回新的DAO即可。
abstract class AbstractDao {
    public abstract String getID();
    public abstract void insert();
    public abstract void update();
    public abstract AbstractDao newInstance();
}
class SqlDao extends AbstractDao {
    public SqlDao() {}
    public String getID() {return "sql";}
    public void insert() {System.out.println("sql insert");}
    public void update() {System.out.println("sql update");}
    public AbstractDao newInstance() { return new SqlDao();}
}

这个仓库可以使用DAO作为工厂,来创建由仓库返回的DAO(在这种情况下,我会将其重命名为Factory),如下所示:

public AbstractDao newDao(String id) {
    return daoMap.containsKey(id) ? daoMap.get(id).newInstance() : null;
}

更新

关于您的问题,关于您的Web服务是否应该实现工厂还是像您描述的那样使用存储库?再次回答取决于细节:

  • 对于Web服务来说,期望有多个并发客户端是正常的
  • 因此,执行两个客户端的过程的实例不得相互影响
  • 这意味着它们不能共享状态
  • 工厂在每个请求上提供一个新的实例,因此当您使用工厂模式时,没有共享状态
  • 如果(仅当)存储库中的实例是无状态的,则您的Web服务也可以像您描述的那样使用存储库。为此,他们可能需要实例化其他对象以根据传递的请求参数实际执行过程。

@rsp-感谢您解决我的问题。 实际上,我想澄清的是何时使用抽象工厂,也就是说我们假设上述两种方法都不会引起一些线程问题。那么,如果问题不存在,为什么要使用抽象工厂呢? 在抽象工厂模式中,不同的产品由不同的工厂创建,在以下方式中(您修复的方式),产品由存储库(例如DaoRepository)创建,那么有什么区别呢?当存储库创建新实例时,它也不知道它正在创建哪个对象,不是吗? - hguser
@hguser,一如既往地,这取决于上下文。如果你需要实例化应用程序使用的对象集合,则使用存储库创建基于配置的实例并提供对这些对象的引用是没有问题的。在这种情况下,“工厂”是存储库初始化的一部分。创建新实例的对象工厂是另一个用例,可能涵盖更多用途。 - rsp
@rsp-所以针对我添加的情况(Web服务示例),如果想在AlgorithmsRepository中使用抽象工厂,应该如何实现呢?我真的很想清楚地说明与相同示例的区别。谢谢。 - hguser
@rsp--是的,我已经考虑了数据共享问题,实际上它需要一些共享数据,例如,多个用户调用“pdf”算法来创建PDF时,PDF算法应该有一个任务队列,我使用静态字段“private static ArrayList taskList”设置了这个任务列表。从您详细的回复中,我感觉我知道一些东西,但我无法掌握它。所以,我想知道是否可以为我的情况提供一个使用AbstractFactory模式的简单设计? - hguser
@hguser,你走在正确的道路上。前进的最佳方式就是尝试构建你到目前为止设计的东西。如果遇到困难,请提出一个新问题,并展示你认为存在问题的代码。 - rsp
@rsp. --谢谢 :). 实际上,该系统已经实现了,Web服务已经在运行。我问这个问题的原因是有人建议我使用抽象工厂。实际上,在我看来,如果抽象工厂更好,我会尝试更改系统。 - hguser

2

如果您要比较UML中的两个设计,那么第二个API在UML中有以下缺点:

  • 调用者需要在调用getDAO()时显式指定DAO的类型。相反,只要DAO符合接口,调用者就不应该关心它所使用的DAO的类型。第一个设计允许调用者简单地调用createDAO()并获取接口以进行操作。这样可以更灵活地控制要使用哪个实现,而调用者不必承担此责任,这提高了设计的整体一致性。

但是抽象工厂还需要调用者注意类型。在这篇文章中,在主方法下,用户必须知道有两种类型:MsWindowsWidgetFactory 和 MACOSWindowsWidgetFactory。 - hguser
@hguser - 在调用链的某个位置,有人需要指定类型,但抽象工厂的直接调用者不需要知道工厂的类型。 - Jeff Sternal
@Jeff Sternal - http://dpaste.org/GyRC/ 第9行和第13行,我们使用“new”关键字手动调用工厂。 - hguser
@hguser:抽象工厂的目的是将**GUIBuilder*与特定实现解耦(不是您的高级应用程序 - 它实际上并不使用*工厂)。您提出的存储库方法需要GUIBuilder知道您想要哪种类型的工厂。 - Jeff Sternal

0

如果您需要在创建某些东西时分离多个选择维度,则抽象工厂很有用。

在窗口系统的常见示例中,您想为各种窗口系统制作小部件系列,并为每个窗口系统创建一个具体工厂,该工厂创建适用于该系统的小部件。

对于构建DAO的情况,如果您需要为域中的各种实体制作DAO系列,并且希望制作整个系列的“sql”版本和“access”版本,则可能非常有用。我认为这是您的同学想要表达的重点,如果是这样,这很可能是个好主意。

如果只有一件事情变化,那就过度了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接