抽象化
抽象是好的软件设计的关键要素之一。 它有助于封装行为。 它有助于解耦软件元素。 它有助于拥有更多独立的模块。 以及更多。
抽象使应用程序可以更轻松地扩展。 它使重构更加容易。 当使用更高级别的抽象进行开发时,您将传达行为而不是实现。
一般
在本文中,我想介绍一个简单的场景,该场景说明如何通过选择一个简单的解决方案来解决硬耦合和刚性设计的情况。
然后,我将简要描述如何避免这种情况。
案例研究说明
假设我们有一个称为RawItem的域对象。
public class RawItem {
private final String originator;
private final String department;
private final String division;
private final Object[] moreParameters;
public RawItem(String originator, String department, String division, Object... moreParameters) {
this.originator = originator;
this.department = department;
this.division = division;
this.moreParameters = moreParameters;
}
}
前三个参数代表项目的密钥。 即,一个项目来自发起者,部门和部门。 “ moreParameters”只是为了强调该项目具有更多参数。
这个三元组有两个基本用法:
- 作为存储在数据库中的键
- 作为映射中的键(RawItem的键)
根据密钥存储在数据库中
DB表被分片以便均匀分配项目。 分片是通过哈希键模函数完成的。 此函数对字符串起作用。
假设我们有N个分片表:(RAW_ITEM_REPOSITORY_00,RAW_ITEM_REPOSITORY_01,..,RAW_ITEM_REPOSITORY_NN),
然后我们将根据一些函数和模分配项目:
String rawKey = originator + "_" + department + "_" + division;
// func is String -> Integer function, N = # of shards
// Representation of the key is described below
int shard = func(key)%N;
在地图中使用按键
三元组的第二种用法是映射项目以进行快速查找。 因此,当不使用抽象时,映射通常如下所示:
Map<String, RawItem> mapOfItems = new HashMap<>();
// Fill the map...
“提高”班级
我们看到密钥通常以字符串形式使用,因此我们决定将字符串表示形式放入RawItem中。
// new member
private final String key;
// in the constructor:
this.key = this.originator + "_" + this.department + "_" + this.division;
// and a getter
public String getKey() {
return key;
}
设计评估
这里有两个流程:
- 分片分布与项目映射之间的耦合
- 映射键很严格。 任何更改都会迫使密钥发生更改,这可能会导致难以发现错误
然后是新的要求
到目前为止,三元组:发起人,部门和部门构成了项目的关键。 但是现在有了新的要求。一个部门可以细分 。 这意味着,与以前不同,我们可以从同一个三元组中获得两个不同的项目。 这些项目将因细分属性而异。
难以改变
关于数据库分发,我们需要保留三元组的串联键。 我们必须保持模函数不变。 因此,分发将继续使用三元组,但是架构将发生变化,并且也具有“细分”列。 我们将更改查询以将细分与原始键一起使用。
关于映射,我们需要进行大量的重构,并传递一个ItemKey (参见下文),而不只是String。
密钥的抽象
让我们创建ItemKey
public class ItemKey {
private final String originator;
private final String department;
private final String division;
private final String subdivision;
public ItemKey(String originator, String department, String division, String subdivision) {
this.originator = originator;
this.department = department;
this.division = division;
this.subdivision = subdivision;
}
public String asDistribution() {
return this.originator + "_" + this.department + "_" + this.division;
}
}
和,
Map<ItemKey, RawItem> mapOfItems = new HashMap<>();
// Fill the map...
// new constructor for RawItem
public RawItem(ItemKey itemKey, Object... moreParameters) {
// fill the fields
}
经验教训和结论
我想展示一个简单的决定如何真正地伤害人。
并且,通过一个很小的变化,我们如何使密钥成为抽象。 将来,键可以具有更多字段,但是我们只需要更改其内部实现即可。 逻辑和映射用法不应更改。
关于更改过程,我没有描述如何进行重构,因为它实际上取决于代码的外观和测试量。 在我们的案例中,某些部分很简单,而其他部分则很困难。 困难的部分围绕着在键(字符串)和项的实现中深入研究的代码。
这种情况是真实的
实际上,我们的设计中就有这种流程。 两年来一切都很好,直到我们不得不更改密钥(添加细分)为止。 幸运的是,我们所有的代码都经过了测试,因此我们可以看到有什么问题并加以解决。 但这很痛苦。
我们最初可以实现两种抽象:
- 更明显的是使用KEY类(如上所述)。 即使只有一个String字段
- 需要检查任何地图用法,是否可以通过使用抽象将其隐藏来受益
第二个抽象更难掌握,难以完全理解和实现。
因此,请进行抽象,讲故事并使用接口,并且讲讲时不要陷入细节。
翻译自: https://www.javacodegeeks.com/2014/04/why-abstraction-is-really-important.html