TypeScript 中实用的 SOLID(二):开放/封闭原则

Image

继续我们的旅程,介绍一个加强应用程序的灵活性的原则:开放/封闭原则。

许多不同的方法和原则可以实现代码的长期改进,其中一些在软件开发社区中广为人知,而另一些则仍然有些不为人知。

在我看来,这与开放/封闭原则(The Open/Closed Principle)的情况一样,由SOLID中的字母O代表。根据我的经验,只有那些真正对SOLID原则感兴趣的人才会理解这个原则的含义。

在某些情况下,我们可能在没有意识到的情况下就应用了这个原则,比如在使用策略模式时。然而,策略模式只是开放/封闭原则的一种应用。

在本文中,我们将深入研究这一原则的全部目的,并使用 TypeScript 中提供的所有示例。

怎么算违反开/闭原则

Image

开放/封闭原则(OCP)指出,我们应该能够在不修改系统的情况下扩展系统的行为。

我们看到上面的OCP需求,是Uncle Bob在他的博客中提供的,乍一看,这似乎是一个很荒谬的需求,我们怎么能在不修改原有代码的情况下扩展它呢?

为了理解开/闭原则的真正目的,让我们检查下面的代码示例,在那里我们可以看到一些不遵守该原则的结构意味着什么,以及可能的后果:

使用 AuthenticationService违反OCP

 
class AuthenticationService {  public authenticate(request: http.ClientRequest, authType: string): User {    switch (authType) {      case 'jwt':        return this.authenticateWithBearerToken(req);      case 'basic':        return this.authenticateWithBasicAuth(req);      case 'applicationKey':        return this.authenticateWithApplicationKey(req);       }        throw new InvalidAuthTypeException();  }    private authenticateWithBearerToken(req http.ClientRequest): User {    // ...  }
  private authenticateWithBasicAuth(req http.ClientRequest): User {    // ...  }    private authenticateWithApplicationKey(req http.ClientRequest): User {    // ...  }
 

这个例子展示了一个类, AuthenticationService 。它应该找出谁是经过身份验证的用户,以访问一些资源,这取决于Web应用程序的 HTTP request 和 authType 。

方法  authenticate  检查  User  是否与  request  中的数据相关联。从  request  中检索  User  可能会因用户是否使用承运人令牌、基本授权或应用程序密钥进行授权而有所不同。

假设我们想要扩展身份验证逻辑并添加一些新的流,例如在会话中保存用户数据。在这种情况下,我们需要在  AuthenticationService  中进行调整。

这样的实施带来了一系列的问题:

  1. AuthenticationService  使逻辑最初在其他地方处理。

  2. 任何授权逻辑的改编,可能在不同的模块中,都需要在  AuthenticationService  中进行改编。

  3. 为了添加提取  User  的新方法,我们总是需要修改  AuthenticationService 。

  4.  AuthenticationService  中的逻辑不可避免地随着每个新的身份验证流而增长。

  5. 对  AuthenticationService  的单元测试包含了太多关于不同权限提取的技术细节。


如何遵循开放/封闭原则
 

Image

开放/封闭原则指出,软件结构应该对扩展开放,但对修改封闭。

有些解决方案应该提供一些东西,以允许从外部进行扩展。在面向对象编程中,我们通过对相同的接口使用不同的实现来支持这种扩展。换句话说,我们使用多态性。

修改  AuthenticationService

 
interface AuthenticationProvider {  getType(): string;  authenticate(req http.ClientRequest) string[];}
class AuthenticationService {  constructor(private providers: AuthenticationProvider[]) {}    public authenticate(request: http.ClientRequest, authType: string): User {    for (const provider of this.providers) {      if (authType === provider.getType()) {        return provider.authenticate(request);      }    }        throw new InvalidAuthTypeException();  }}
 

在上面的例子中,我们可以看到一个候选者尊重了开放/封闭原则,类  AuthenticationSerivce  不再包含关于提取  User  的技术细节。

相反,我们引入了一个新的接口, AuthenticationProvider ,它包含了不同 User 提取的逻辑。例如,它可以是 BearerTokenProvider ,或者 ApiKeyProvider ,或者 BasicAuthProvider 。

现在,负责身份验证的模块也可以包含  Users  的提取器,另一方面,扩展  AuthenticationService  而无需修改它的主要目标现在是可能的。

函数的开闭原则
 

Image

我们可以将开放/封闭原则应用于孤立的方法,而不仅仅是结构体/类。

使用 getCities违反OCP

 
const getCities = async (sourceType: string, source: string): Promise<City[]> => {  let data: string;    if (sourceType === 'file') {    const buffer = fs.readFile(source);    data = buffer.toString();  } else if (sourceType === 'link') {    data = await fetch(source);  }    const list = JSON.parse(data);  const cities: City[] = [];    for (const item of list) {    cities.push(new City(      item.name,      item.longitude,      item.latitude,      item.countryCode    ))  }    return cities;}
函数 GetCities 从某个源读取城市列表,这个源可以是一个文件或者是互联网上的某个资源,当然,我们将来也可能从内存、Redis或者其他任何源读取数据。

因此,最好将读取原始数据的过程变得更加抽象。因此,我们可以从外部提供一个读取策略作为方法参数。

修改 getCities

 
type DataReader = (source: string) => Promise<string>;
const readFromFile = async (fileName: string): Promise<string> => {  const buffer = fs.readFile(fileName);  return buffer.toString();}
const readFromLink = async (link: string): Promise<string> => {  return await fetch(link).then(response => response.text());}
const getCities = async (reader: DataReader, source: string): Promise<City[]> => {  const data = await reader(source);    const list = JSON.parse(data);  const cities: City[] = [];    for (const item of list) {    cities.push(new City(      item.name,      item.longitude,      item.latitude,      item.countryCode    ))  }    return cities;}
 

正如你在上面的解决方案中看到的,我们定义了一个新的类型 DataReader ,表示从某个源读取原始数据的函数。

新的函数 ReadFromFile  和 ReadFromLink  是 DataReader  类型的实际实现。

 GetCities  方法希望将  DataReader  的实际实现作为参数,然后在函数体内执行并获取原始数据。

结论
 

开放/封闭原则(OCP)确实是SOLID原则中的一个关键原则,强调了在不修改现有代码结构的情况下进行扩展的设计软件的重要性。

它促进了多态的使用和创建清晰的接口,以实现这种可扩展性。OCP有助于随着需求的变化和新功能的添加,使软件更具适应性和可维护性。

 欢迎关注公众号:文本魔术,了解更多

  • 16
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值