oop - 如何对区域和该区域中的点进行建模?

标签 oop language-agnostic domain-driven-design

我需要对具有 contains(point) 方法的 Region 进行建模。该方法确定一个点是否落在 Region 的边界内。

我目前看到 Region 的两种实现:

  • 其中区域由起始和结束邮政编码定义。
  • 其中区域由纬度/经度和半径定义。

现在,我的主要问题是如何定义 contains() 方法的接口(interface)。


可能的解决方案#1:
一个简单的解决方案是让一个点也由一个区域定义:

PostalcodeRegion implements Region

region = new PostalcodeRegion(postalStart: 1000, postalEnd: 2000);
point = new PostalcodeRegion(postalStart: 1234, postalEnd: 1234);
region.contains(point); // true

界面可能如下所示:

Region
+ contains(Region region):bool

问题在于 contains() 方法并不具体,而且我们滥用 Region 让它成为它不是的东西:Point


可能的解决方案#2:

或者,我们定义一个新的点类:

PostalcodeRegion implements Region {}
PostalcodePoint implements Point {}

region = new PostalcodeRegion(postalStart: 1000, postalEnd: 2000);
point = new PostalcodePoint(postalCode: 1234);
region.contains(point); // true

界面:

Region
+ contains(Point point)

这种方法有几个问题:

  • contains()方法仍然不具体
  • 有一个毫无意义的Point概念。它本身什么也不做,它只是一个标记界面。

澄清: 好吧,这是我第一次遇到我以可能的解决方案的形式提供我的思路,这实际上是适得其反的。我很抱歉。

让我尝试描述一下用例:该用例所属的系统用于处理保险 claim (除其他外)。当有人声称因管道泄漏而造成水损坏时,该系统会处理从客户输入到发送维修公司等关闭文件的整个工作流程。

现在,根据情况,有两种方法可以找到符合条件的维修公司:通过邮政编码,或通过 lat lng。

在第一种情况(邮政编码)中,我们可以通过以下代码找到符合条件的维修公司:

region = new PostalCodeRegion(customer.postalCode - 500, customer.postalCode + 500)
region.contains(new PostalCodePoint(repairCompany1.postalCode))
region.contains(new PostalCodePoint(repairCompany2.postalCode))

或者,在第二种情况下:

region = new LatLngRegion(customer.latLng, 50) // 50 km radius
region.contains(new LatLngPoint(repairCompany1.latLng))
region.contains(new LatLngPoint(repairCompany2.latLng))

我希望能够安全地传递 RegionPoint,这样我就可以确保它们是 Region。但我实际上并不关心它们的子类型。


我想要的一件事是不必对 contains() 中传递的 point 进行运行时检查,但我不确定这是否可能方法。最好通过契约(Contract)强制执行,让我获得要使用的正确数据(适合所选的区域实现)。


我大多只是大声思考。我倾向于使用方法 #2,并对 contains() 实现中传递的 point var 进行运行时类型检查。

我想听听一些关于其中一个或另一个的想法,甚至更好的想法:一个我没有想到的新建议。

它不应该真正相关,但目标平台是 PHP。所以我不能使用泛型。

最佳答案

鉴于Region必须对两个没有任何共同点的抽象进行操作( PointPostcode ),那么通用接口(interface)是构建干净的强类型通用接口(interface)的一种方法,但是您应该质疑该抽象对于建模是否有用。作为开发人员,很容易迷失在太多的抽象中,例如也许是Region<T>只是一个 Container<T>等等,突然之间,您使用的概念在您的域的 Ubiquitous Language 中找不到了。 .

public interface Region<T> {
    public boolean contains(T el);
}

class PostalRegion implements Region<Postcode> {
    public boolean contains(Postcode el) { ... }
}

class GeographicRegion implements Region<Point> {
    public boolean contains(Point el) { ... }
}

此类问题的问题在于,它关注的是如何实现特定的设计,而不是解释真正的业务问题,这使得很难判断解决方案是否合适或哪种替代解决方案合适。

如果实现了通用接口(interface),系统将如何利用该接口(interface)?它会让模型更容易使用吗?

由于我们被迫假设一个问题域,所以这里有一个关于开发城市分区系统的虚构场景(我对这个域一无所知,所以这个例子可能很愚蠢)。

In the context of city zonage management, we have uniquely identified regions that are defined by a postal code range and a geographical area. We need a system that can answer whether or not a postal code and/or a point is contained within a specific region.

这为我们提供了更多的背景,可以帮助我们提出一个可以满足需求的模型。

enter image description here

我们可以假设一个应用程序服务,例如 RegionService可能看起来像:

class RegionService {
    IRegionRepository regionRepository;
    ...

    boolean regionContainsPoint(regionId, lat, lng) {
        region = regionRepository.regionOfId(regionId);
        return region.contains(new Point(lat, lng));
    }

    boolean regionContainsPostcode(regionId, postcode) {
        region = regionRepository.regionOfId(regionId);
        return region.contains(new Postcode(postcode));
    }
}

那么,也许设计会受益于应用 Interface Segregation Principle (ISP)在那里你会有一个 Locator<T>接口(interface)或显式PostcodeLocatorPointLocator接口(interface),由 Region 实现或其他服务并由 RegionService 使用或者成为他们自己的服务。

如果回答问题需要复杂的处理等,那么也许应该从 PostalRange 中提取逻辑。和一个Area 。应用 ISP 将有助于保持设计更加灵活。

值得注意的是 domain model闪耀write side保护不变量并计算复杂的规则和状态转换,但查询需求通常更好地表达为利用强大的基础设施组件(例如数据库)的无状态服务。

编辑1:

我没有意识到你提到“没有泛型”。仍然把我的答案留在那里,因为我认为它仍然提供了很好的建模见解,并警告了不太有用的抽象。始终考虑客户端将如何使用 API,因为它有助于确定抽象的有用性。

编辑2(添加澄清后):

看来 Specification Pattern在这里可能是一个有用的建模工具。 可以说客户拥有维修公司资格规范...

例如

enter image description here

class Customer {
    ...
    repairCompanyEligibilitySpec() {
        //The factory method for creating the specification doesn't have to be on the Customer
        postalRange = new PostalRange(this.postalCode - 500, this.postCode + 500);
        postalCodeWithin500Range = new PostalCodeWithinRange(postalRange);
        locationWithin50kmRadius = new LocationWithinRadius(this.location, Distance.ofKm(50));

        return postalCodeWithin500Range.and(locationWithin50kmRadius);
    }
}

//Usage

repairCompanyEligibilitySpec = customer.repairCompanyEligibilitySpec();
companyEligibleForRepair = repairCompanyEligibilitySpec.isSatisfiedBy(company);

请注意,我还没有真正理解您所说的“我希望能够安全地传递区域和点”的意思,或者至少无法理解为什么这需要一个通用接口(interface),所以也许提议的设计不会是合适的。明确策略/规则/规范有几个优点,并且规范模式可以轻松扩展以支持功能,例如描述公司不符合条件的原因等。

例如

unsatisfiedSpec = repairCompanyEligibilitySpec.remainderUnsatisfiedBy(company);
reasonUnsatisfied = unsatisfiedSpec.describe();

最终规范本身不必实现这些操作。您可以使用 Visitor Pattern为了向一组规范添加新操作和/或按逻辑层隔离操作。

关于oop - 如何对区域和该区域中的点进行建模?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40402341/

相关文章:

排除数字的算法

language-agnostic - 我应该继承还是使用枚举?

php - setter/getter 中重复开关的设计模式?

domain-driven-design - Ncqrs:如何在没有聚合根的情况下引发事件

opengl - opengl中的顶点缓冲区

php - 获取对象php内对象的变量名

php - 如何正确设计这些类?

domain-driven-design - 用标志交换存储库

go - 在DDD(纯净/六角形)架构中处理数据库连接和Env配置

c# - 你需要 ref 或 out 参数吗?