【发布时间】:2021-10-11 21:18:41
【问题描述】:
我很困惑!我试图弄清楚何时应用 Solid Principles 以及何时使用哪种 设计模式(工厂方法、构建器模式等)。我搜索了很多,但我找不到我的问题的答案。最后我在这里发布。 请解释一下。
【问题讨论】:
标签: design-patterns architecture software-design design-principles
我很困惑!我试图弄清楚何时应用 Solid Principles 以及何时使用哪种 设计模式(工厂方法、构建器模式等)。我搜索了很多,但我找不到我的问题的答案。最后我在这里发布。 请解释一下。
【问题讨论】:
标签: design-patterns architecture software-design design-principles
适配器模式 假设您从亚洲前往欧洲,您需要使用亚洲插头连接您的笔记本电脑,但您不能,因为欧洲插座不是为您的插头而设计的。在这种情况下,您可以在插头和插座之间使用适配器。
当您想将代码与不同的接口连接时,您可以使用适配器设计模式。
另一个好处是,使用适配器模式使您的代码依赖于您的适配器而不是外部库,这可能会增加代码的可维护性或可测试性等。
当我不希望我的域代码(业务逻辑)依赖于使用的库(f.ex 数据库 ORM)时,我经常使用适配器。 Adapter 让我可以灵活地更改库,而不会破坏我的域代码。
Facade 与适配器的用法非常相似。当您想通过将复杂代码(使用的几个库)隐藏在简单易用的界面后面来简化代码时,您可以使用外观。这种方法的好处是facade 后面的代码可以很容易地修改,而不会破坏使用这个facade 的代码。使用它可能会增加代码的可读性和可测试性,或者使代码重构更简单。
【讨论】:
我在任何地方都使用 Solid,除了使用 Solid 会导致代码有异味的特殊情况。使用 Solid 通常会使代码更易于维护、阅读和测试。
静态工厂方法 - 一种方便的模式,有多个构造函数,同时用有意义的名称解释它们。
class AClass {
private final String parameter;
private AClass(String parameter) {
this.parameter = parameter;
}
public static AClass fromLong(Long longParameter) {
return new AClass(longParameter.toString());
}
public static AClass fromDecimal(Long longParameter) {
return new AClass(longParameter.toString());
}
public static AClass empty() {
return new AClass(null);
}
}
工厂方法 当我们需要设计一个代码来构造一个实现接口的对象,但是我们只在运行时才知道这个对象的具体实现,那么工厂方法就派上用场了。用户可以通过添加新的具体对象和创建工厂方法来轻松扩展功能。
请参见下面的示例。
DocumentService 可以与任何实现Document 的类以及任何扩展AbstractDocumentCreator 的类一起使用。 当前版本的示例支持 Txt 和 Html 文档。如果我们需要 PFG 支持,我们只需创建一个 PdfDocument 类来实现 Document 和 PdfDocumentCreator 扩展 AbstractDocumentCreator。
在运行期间,PdfDocumentCreator(或任何其他创建者类)实例被传递给 DocumentService::create 方法。该服务使用这个创建者类来创建一个文档,使用它,并将它存储在数据库中。
我们的库是 OPEN/CLOSED(关闭用于修改界面,OPENED 用于在需要时添加对新类型文档的支持)。
class UserData {
private final long userId;
UserData(long userId) {
this.userId = userId;
}
long getUserId() {
return userId;
}
}
interface Document {
void setUserData(UserData userData);
byte[] toBytes();
}
class TxtDocument implements Document {
@Override
public void setUserData(UserData userData) {
//TODO: Implement feature
}
@Override
public byte[] toBytes() {
return new byte[0];
}
}
class HtmlDocument implements Document {
@Override
public void setUserData(UserData userData) {
//TODO: Implement feature
}
@Override
public byte[] toBytes() {
//TODO: Implement feature
return new byte[0];
}
}
class PdfDocument implements Document {
@Override
public void setUserData(UserData userData) {
}
@Override
public byte[] toBytes() {
return new byte[0];
}
}
abstract class AbstractDocumentCreator {
abstract Document create(UserData userData);
}
class TxtDocumentCreator extends AbstractDocumentCreator {
@Override
Document create(UserData data) {
TxtDocument document = new TxtDocument();
document.setUserData(data);
return document;
}
}
class HtmlDocumentCreator extends AbstractDocumentCreator {
@Override
Document create(UserData userData) {
TxtDocument document = new TxtDocument();
document.setUserData(userData);
return document;
}
}
class DocumentService {
private final DocumentRepository userRepository;
DocumentService(DocumentRepository documentRepository) {
this.documentRepository = documentRepository;
}
void saveInDb(AbstractCreator abstractCreator, UserData userData) {
Document document = abstractCreator.create(userData);
documentRepository.insert(document);
}
}
构建器模式 - 当您的类的用户每次使用不同的参数集构建您的类时,构建器模式为他们提供了以他们喜欢的方式构建对象的灵活性。通常用于构建应用程序配置、SQL 查询、HTTP 请求或不可变类。为用户提供灵活性,增加构建类的代码的可维护性,并且通常比参数构造函数或工厂方法等更具可读性和易用性。
List<Person> persons = queryFactory.selectFrom(person)
.where(
person.firstName.eq("John"),
person.lastName.eq("Doe"))
.fetch();
或另一个例子
Book book = new Book.Builder("0-12-345678-9", "Moby-Dick")
.genre(Genre.ADVENTURE_FICTION)
.author("Herman Melville")
.published(Year.of(1851))
.description(
"The book is the sailor Ishmael's narrative of the obsessive quest of "
+ "Ahab, captain of the whaling ship Pequod, for revenge on Moby Dick, "
+ "the giant white sperm whale that on the ship's previous voyage bit "
+ "off Ahab's leg at the knee."
)
对于使用哪种设计模式没有严格的指导;因此,使用它们的能力来自经验和实验。在不同的上下文中使用设计模式并与其他开发人员讨论您的代码,您将了解何时使用哪种解决方案。
【讨论】:
其他答案很好,但它们深入细节,所以让我给你一些背景。
设计原则(如 SOLID)就像简单的低级通用规则:
设计模式更高层次,更具体:
警告:通常会看到使用特定语言代码描述的通用设计模式 - 作为一种交流方式,而不是因为该模式的基本思想是该语言独有的。就我个人而言,我会说有意特定于技术的设计模式(不能轻易用于不同的技术)是真正的参考模式。
【讨论】:
SOLID 原则很好,原则。他们就是这样。它们就像是检验系统设计是否良好的试金石。它们也可以用于建筑。例如,一个类应该有一个单一的职责。如果一个班级做不止一件事,这意味着该班级有问题。所以 SOLID 原则不会直接告诉你该怎么做,而是指出这里出了点问题——这种气味会带你进入一个很深的兔子洞,所以避免它,你明白了。
另一方面,设计模式不像 SOLID 原则那样通用,但它们是针对常见问题的精炼解决方案。正如算法是针对常见问题的精炼解决方案一样,就像数据结构是用于保存不同类型数据的精炼结构一样,设计模式是针对常见设计问题的精炼解决方案。大多数时候,该设计问题可以追溯到一个或多个 SOLID 原则。但这并不意味着设计模式涵盖了“所有”类型的 SOLID 原则的解决方案。 SOLID 原则更广泛。
对 SOLID 原则和设计模式的另一种看法是,当您使用 SOLID 原则清理代码时,生成的代码将被重构,然后您将能够轻松发现设计问题的应用。
什么是依赖?类 X 有一个方法 A,它在某些时候引用类 Y -> X 对 Y 有依赖关系。这种依赖关系可以是透明的,也可以隐藏在糟糕的设计之下,例如违反 Liskov 替换原则的补丁。因此,一旦您删除了该违规行为,您就会看到可以应用设计模式的设计问题。如果不使用 SOLID 原理,X 对 Y 的依赖仍然存在。设计问题没有解决——更糟糕的是——它就在那里,但它被隐藏了。现在使用 SOLID 原理后,隐藏的设计问题就暴露出来了,然后你就可以适当地解决它。这只是一个例子。
【讨论】: