我想对下面的域驱动设计提出一些建议和批评。我在下面包含了伪代码。真正的代码将具有封装属性。
我唯一担心的是它似乎是贫血。
enum UnitedStatesState {
ALABAMA,
//...
CALIFORNIA,
//...
MAINE,
//...
WASHINGTON
}
class License {
int id;
String name;
//enum of state that the license is applicable in
UnitedStatesState state;
}
class LicenseRequest {
//the name of the person making the request
String name;
//enum of state to which the user is requesting a license in
UnitedStatesState state;
LicenseResponse submit()
{
//TODO: move creation of the rules out of this class
RuleGroup<LicenseRequest> ruleGroup = new RuleGroup<>();
ruleGroup.add(new StateExclusionLicenseRequestRule(UnitedStatesState.MAINE));
boolean approved = ruleGroup.execute(this);
if(approved) {
License license = createLiscense(request);
return new ApprovedLicenseResponse(license);
} else {
DeniedLicenseResponse response = new DeniedLicenseResponse();
response.rules = newArrayList(ruleGroup);
return response;
}
}
//TODO: move create license out of Request. maybe a factory class?
private License createLicense()
{
License license = LicenseIdGenerator.generate(this.state);
license.name = this.name;
license.state = this.state;
save(license);
return license;
}
}
//visitor for the rule
interface Rule<T> {
public boolean execute(T o);
public List<String> getMessages();
}
//rule that auto denies when the request is made in an excluded state
class StateExclusionLicenseRequestRule : Rule<LicenseRequest> {
public List<String> getMessages();
UnitedStatesState excludedState;
public boolean execute(LicenseRequest request) {
if(request.state == excludedState)
{
messages.add("No license for " + request.state + " is available at this time.");
return false;
}
return true;
}
}
//rule that groups all other rules
class RuleGroup<T> : Rule<T> {
public void addRule(Rule<T> rule);
public List<Rule<T>> getFailedRules();
public List<String> getMessages() {
List<String> messages = new ArrayList<>();
for(Rule<T> rule : rules) {
messages.addAll(rule.getMessages());
}
return messages;
}
public boolean execute(T o) {
List<Rule<T>> failedRules = new ArrayList<>(rules.size());
for(Rule<T> rule : rules) {
boolean approve = rule.execute(o);
if(!approve) {
failedRules.add(rule);
}
}
return !failedRules.isEmpty();
}
}
interface LicenseResponse {
boolean approved;
}
class ApprovedLicenseResponse : LicenseResponse {
License license;
}
class DeniedLicenseResponse : LicenseResponse {
private List<Rule<LicenseRequest>> rules;
public List<String> getMessages()
{
List<String> messages = new ArrayList<>();
for(Rule<LicenseRequest> rule : rules) {
messages.addAll(rule.getMessages());
}
return messages;
}
}
request = new Request(name: 'Test', state: UnitedStatesState.CALIFORNIA)
response = request.submit()
if(response.approved)
{
out('Your request is approved');
out('license id = ' + reponse.id);
}
else
{
out('Your request was denied');
for(String message : response.messages)
{
out(message);
}
}
这只是对我想要实现的内容的嘲弄。这是一个简单的系统,用户将信息输入到关于他们自己的表单中,并且他们被批准或拒绝许可。批准后,可以打印证书。
为了举例,唯一的规则是拒绝在缅因州申请许可证。
我对上面的例子做了一些修改。删除了Handler并将所有代码移动到LicenseRequest。我还将批准/拒绝的规则移到了实现vistor模式的类中。
答案 0 :(得分:2)
不幸的是,没有显示一些更相关的代码,但我会查看可以将哪些代码推送到LicenseRequest
。特别是,LicenseRequest
可能会创建License
而不是处理程序(可能通过为其提供ID)。如果LicenseRequest
的属性仅用于创建已批准的许可证,则尤其如此。然后这些不必暴露于吸气剂。
我也有determineApproval
(可能有另一个名字)直接创建响应,而不是传递可写消息列表(仅用于失败)。
你应该寻找的气味是Feature Envy。特别是,应检查使用License
或LicenseRequest
数据的任何计算,以确定是否应在这些类中进行计算。
数据对象(特别是不可变数据对象)有一个目的,但您应该关注它。
答案 1 :(得分:0)
状态应该是可扩展性的ValueObjects。例如,当您需要将州的缩写与其名称等同时会发生什么?
看起来您的设计确实没有聚合根。如果没有许可证,LicenseRequest是否毫无意义?在这种情况下,它应该通过许可证(或许可证服务)处理,而不是直接由调用代码(可以是Web服务器,控制台应用程序等)处理。
因此伪代码会像
var license = licenseFactory.NewLicense();
response = license.Request(name, state)
更有意义吗?如果您拥有多种类型的许可证,例如商业驱动程序,会发生什么?
此外,对于规则而不是命令模式(执行),您可以通过将规则作为规范来获得更清晰的解决方案 - https://en.wikipedia.org/wiki/Specification_pattern