【问题标题】:Facade or Decorator立面或装饰
【发布时间】:2015-04-28 07:10:13
【问题描述】:

上下文:

我有一个 REST 服务,比如说 CustomerService,它现在有一个方法 getCustomer(id, country)。现在的要求是,根据国家/地区,我必须执行不同的业务逻辑,例如访问不同的数据库或一些自定义规则,然后报告我收到了这样的请求。

首先根据国家/地区解决不同的实现,我使用了工厂模式,如下所示:

所有国家/地区实施的通用界面

public Interface CustomerServiceHandler{
   Cusomer getCustomer(String id, String country);
}

然后工厂为

public class CustomerServiceHandlerFactory{
   public CustomerServiceHandler getHandler(String country){...};
}

使用 Facade 的实现细节

注意这个外观是从 REST 类调用的,即CustomerService

public CustomerServiceFacade{
   public Customer getCustomer(String id, String country){
        //use factory to get handler and then handler.getCustomer
        Customer customer = factory.getHandler(country).getCustomer(id,country);
        //report the request
        reportingService.report('fetch-customer',....);
        return customer;
   }
}

按照 SRP(单一职责原则),这个门面没有实现单一目标。它正在接受客户并报告已收到此类请求。所以我想到了装饰器模式如下。

使用装饰器模式实现:

//this is called from Rest layer
public ReportingCustomerHandler implements CustomerServiceHandler{
    //this delegate is basically the default implementation and has factory too                    
    private CustomerServiceHandler delegate;
    private ReportingService reporting;
    public Customer getCustomer(String id, String country){
           Customer customer = delegate.getCustomer(id, country);
           reporting.report(....);
           return customer;
    }
}


 //this is called from ReportingCustomerHandler           
 public DefaultCustomerServiceHandler implements CustomerServiceHandler{
     private CustomerServiceHandlerFactory factory;                    

     public Customer getCustomer(String id, String country){
         //get factory object else use itself, even default implementation is provided by factory
         CustomerServiceHandler handler = factory.getHandler(country);
         return handler.getCustomer(id,country);

     }
 }

注意:在第二种方法中,我也将接口CustomerServiceHandler(显示在工厂代码中)用于Reporting and Default implementations

那么正确的方法是什么,或者如果存在更合适的方法,还有什么替代方法。

问题的第二部分

如果我必须维护两个不同的接口,即一个 CustomerServiceHandler 来实现不同国家/地区的实现,一个用于服务 REST 层,该怎么办。那么什么可以是设计或替代方案。在这种情况下,我认为外观会合适。

【问题讨论】:

    标签: java design-patterns decorator facade


    【解决方案1】:

    那么正确的方法是什么,或者有什么替代方法

    你在这里有一个可靠的设计并且很好地使用了工厂模式。我提供的是对这项好工作的建议,但我认为有很多方法可以增强你所拥有的。

    我可以看到 CustomerServiceFacade 方法 getCustomer 在哪里破坏了 SRP。它结合了检索客户和报告方面。我同意将报告从该方法中移出会更干净。

    那么您的对象将如下所示:

    public CustomerServiceFacade{
       public Customer getCustomer(String id, String country){
            return factory.getHandler(country).getCustomer(id,country);
       }
    }
    

    那么我们将报告放在哪里?

    您可以通过单独的界面移动/管理报告。这将允许灵活地实施不同的报告方法并使测试更容易(即模拟报告部分)。

    public interface ReportService {
       void report(Customer c, String id, String country);
    }
    

    REST 层如何访问报告?

    方案一:REST通过多个对象访问各种Customer函数

    ReportService 的实现可以通过CustomerServiceFacade 注入到 REST 控制器中。

    不确定您为 REST 使用的是什么框架,但可能看起来像这样:

    @GET
    @Path("/customer/{Id}/{country}")
    public Response getCustomer(@PathParam("Id") String id, @PathParam("country") String country){
    
        Response r = null;
    
        // injected implementation of CustomerServiceFacade
        Customer c = customerServiceFacade.getCustomer(id, country);
        if (c!=null){
            // injected implementation of ReportService
            reportService.report(c, id, country);
        }
        else {
            // handle errors ...
        }
    
        return r;
    }
    

    选项 2:REST 通过一个外观/服务访问各种客户功能

    您可以让您的服务外观层提供一个功能,即为提供功能的更大对象集提供简化接口。这可以通过使用多种客户服务方法来完成,这些方法使 REST 层能够通过一个对象访问各种功能,但仍然具有使每种方法更紧密地遵循 SRP 的好处。

    这里我们将CustomerServiceFacade 注入到 REST 控制器中,它会调用两种方法:1)获取客户和 2)处理报告。外观使用上面的ReportService 接口的实现。

    public CustomerServiceFacade{
       public Customer getCustomer(String id, String country){
            // call the correct CustomerServiceHandler (based on country)
            return factory.getHandler(country).getCustomer(id,country);
       }
    
       public void report(Customer c, String id, String country){
            // call the reporting service 
            reportService.report(c, id, country);
       }
    }
    

    我认为这是对外观模式的合理使用,同时在实际方法中仍然具有 SRP。

    如果报告实施因国家/地区而异,就像客户一样,您可以使用其他工厂。

       public void report(Customer c, String id, String country){
            // call the correct reporting service (based on country)
            rptFactory.getInstance(country).report(c,id,country);
       }
    

    【讨论】:

    • 感谢您的意见。关于报告,我不想将它移到休息层,因为我喜欢让休息层独立于任何逻辑,以便轻松地用任何实现切换该接口。另外你对装饰器模式有什么看法?
    • 装饰器可以工作,但你的ReportingCustomerHandler 调用了getCustomerreport,这模仿了我们在CustomerServiceFacade 中遇到的“问题”,所以我试图设计掉尽可能地从那里。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多