OOP抽象那一套没有你想象的那么万能

本文首发于:造梦网
欢迎关注公众号:通用代码技术

假如你正在做一个游戏项目,你会有许多的物品类 Object 和 玩家类 Player ,它们各自有不同的功能,也有一些共同的功能。此时为了代码复用,减少代码重复,很多架构师都会考虑使用 继承 来复用它们之间重复的代码。

这就是抽象 Abstraction,而你刚刚创建了一个抽象。

抽象带来的问题

架构师非常擅长识别重复的内容,并将其提取出来。对于代码来说,代码重复是糟糕的,而更多的抽象意味着更高的代码复用,这是公认的结论。

但是还有一个没有被考虑的问题:耦合(COUPLING)。

大多数人从概念上理解了什么是耦合,并且在尝试重构过度耦合的系统的时候,他们也感受到了耦合的存在。但是在设计软件的时候,并不能直接感受到耦合的影响

耦合和抽象的关系

耦合是抽象的一种附加效应,简单来说就是正比关系。

更多的抽象就意味着更多的耦合。

假如你现在有一个类 SaveXML:

using System.Text.Json;
using System.IO;

class SaveXML
{
  privete string fileName;
  public SaveXML(string fileName)
  {
    this.fileName = fileName;
  }
  public void save(GameState state)
  {
    XmlWriterOptions option = new XmlWriterOptions();
    ...
  }
}

此时由于架构升级,需要使用 SaveJSON 类。

新建一个类

如果直接新建一个 SaveJSON 类的话,那么可以很轻松的发现这两个类的大部分代码是相同的,而且智能 IDE 也可以检测出来部分重复代码。

using System.Text.Json;
using System.IO;

class SaveJSON
{
  privete string fileName;
  public SaveJSON(string fileName)
  {
    this.fileName = fileName;
  }
  public void save(GameState state)
  {
    JsonWriterOptions option = new JsonWriterOptions();
    ...
  }
}

显然这是不优雅的。

抽象尝试

我们的直觉就是创建一个名为 FileSaver 的通用类作为上述类的父类。

class FileSaver
{
  protected string filename;
  
  public FileSaver(string filename)
  {
    this.filename = filename;
  }
}

FileSaver 的子类可以从这个受保护变量中获取文件名 filename

using System.Text.Json;
using System.IO;

class SaveXML : FileSaver
{
  public SaveXML(string fileName)
    : base(filename)
  { }
  public void save(GameState state)
  {
    XmlWriterOptions option = new XmlWriterOptions();
    ...
  }
}

class SaveJSON : FileSaver
{
  public SaveJSON(string fileName)
    : base(filename)
  {}
  public void save(GameState state)
  {
    JsonWriterOptions option = new JsonWriterOptions();
    ...
  }
}

但这是一个糟糕的做法。

我们现在将两个类获取文件名filename的途径耦合到了相同的输入源 - 父类 FileSaver

而且这种改变并没有任何实质性的作用,对我们没有任何帮助。

再重构

现在我们考虑写一个 save 接口。

class FileSaver
{
  void save(GameState state);
}

我们知道,这也会增加两个类之间的耦合。因为现在,这两个类都被限制在了同一个 save 方法中。如果后期需要修改,一旦修改了接口,所有与该接口有关的类都需要进行更改。这种做法没有带来任何好处。

该类的使用

看看这个重构之后的类是如何使用的。

void Save(GameConfig gameConfig)
{
  FileSaver saver;
  if (gameConfig.saveMode == SaveMode.Xml)
  {
    saver = new SaveXML("gameSave.xml");
  }
  else if (gameConfig.saveMode == SaveMode == SaveMode.Json)
  {
    saver = new SaveJSON("gameSave.json")
  }
  else
  {
    throw new ArgumentException("Invalid save option");
  }
  
  saver.save(state);
}

可以看到,这种抽象并没有有效的简化程序。

在这个问题上,最好的做法就是:

将它们保留为两个完全没有联系的不同类。

值得抽象的两种情况

有三种以上类型的情况

如果我们有三个以上相似的类,那么它确实值得我们去抽象。

void Save(GameConfig gameConfig)
{
  switch(gameConfig.saveMode)
  {
    case SaveMode.Xml:
      SaveXML xml = new SaveXML(gameConfig.saveUrl);
      xml.save(state);
      break;
    case SaveMode.Json:
      ...
    case SaveMode.SqlLite:
      ...
    case SaveMode.AWS:
      ...
    default:
      throw new ArgumentException("Invalid save option");
  }
}

我们可以考虑将这些代码提取到单独的代码段:

class SaverFactory
{
  private GameConfig.config;
  
  public SaverFactory(GameConfig config)
  {
    this.config = config;
  }
  public FileSaver create()
  {
    switch(config.saveMode)
    {
      case SaveMode.Xml:
        return new SaveXML(config.saveUrl);
        break
      case SaveMode.Json:
        return new SaveJSON(config.saveUrl);
        break;
      case SaveMode.SqlLite:
        return new SqlLiteSaver(config.saveUrl);
        break;
      case SaveMode.AWS:
        return new AWSSaver(config.saveUrl);
        break;
      default:
        throw new ArgumentException("Invalid save option");
    }
  }
}

此时,使用这项功能的代码就可以重构为:

void Save(GameConfig gameConfig)
{
  SaverFactory factory = new SaverFactory(gameConfig);
  FileSaver saver = factory.create();
  saver.save();
}

需要延迟或者重复保存

例如,如果我们希望程序每五分钟自动保存一次。那么此时的定时器不知道这个 saver 对象是谁,也许有很多种类型的对象。那么这时候,抽象确实会使程序变得简洁。

结语

总的来说,最好只在抽象带来的价值超过耦合时才应用抽象

这意味着,在没有使用抽象的时候,代码可能会有一些重复。

但是我认为,在修改代码时,少量的代码重复要比过度耦合带来的痛苦要小得多。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

通用代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值