软件设计原则:单级抽象
#编程 #java #cleancode #designpatterns

单一级别的抽象(SLA)是一种软件设计原则,它强调在功能中具有同一抽象水平的表达式的重要性,旨在提高可读性。它还确保它符合单一责任(SR)原则。

在与此主题相关的其他文章中,它们通常会参与示例,而无需解释抽象水平的含义。幸运的是,我们不会跳过最重要的一点。

什么是抽象水平?

每个函数都执行一个操作,通常包括子动作。这些子侵入的水平表明抽象水平。本质上就是这样。

让我们用咖啡机的示例来说明这一点。

Example

如果我们想从机器上购买咖啡,我们将钱投入,选择我们想要的类型,等待它做好准备,然后服用。这些步骤处于抽象的第一级,因为它们是这种情况的基本步骤。

这些用户交互在后台触发其他操作。在咖啡准备阶段,机器拿起杯子,加咖啡,倒入热水和洗牌。这些步骤位于抽象的第二层(货币插入的子分数是相同的)。它们是想要购买咖啡的用户不应该关注的行动。如果将杯子放入机器或自己搅拌咖啡,那将是一个不必要的细节。但是,如果机器中没有杯子,则需要检查相关功能。

让我们看看违反原理的指令序列伪造代码。

getCoffee() {
  checkMoney();      // abstraction lvl 2
  addToBallance();   // abstraction lvl 2
  chooseCoffeType(); // abstraction lvl 1
  getGlass();        // abstraction lvl 2
  putCoffee();       // abstraction lvl 2
  putWater();        // abstraction lvl 2
  shuffleGlass();    // abstraction lvl 2
  giveCoffee();      // abstraction lvl 1
}

我们不会自己添加平衡或检查钱,对吗?因此,这是从用户那里抽象的。让我们重构代码以添加额外的抽象。

getCoffee() {
  getMoney();        // abstraction lvl 1
  chooseCoffeType(); // abstraction lvl 1
  prepareCoffee();   // abstraction lvl 1
  giveCoffee();      // abstraction lvl 1
}

代码示例

calculateSum()符合SLA原理,仅包括计算总和的细节。它定义了一个保存计算结果的变量,将其他数字添加到其中。

public int calculateSum(int[] numbers) {
  int sum = 0;

  for (int num : numbers) {
   sum += num;
  }

  return sum;
}

,但是validateUser()包含不同的抽象水平。 isValidEmail()处于抽象的第二级。该功能的主要目的是检查用户是否有效。但是,在查看代码时,我们还会看到验证过程和错误处理的详细信息,表明存在多个抽象级别。要比较其可读性,请仔细研究validateUser()功能。

public boolean validateUser(User user) {
  if (user.getName().isEmpty()) {
    System.out.println("Name cannot be empty.");
    return false;
  }

  if (user.getEmail().isEmpty()) {
    System.out.println("Email cannot be empty.");
    return false;
  }

  if (!isValidEmail(user.getEmail())) {
    System.out.println("Invalid email format.");
    return false;
  }

  if (user.getPassword().isEmpty()) {
    System.out.println("Password cannot be empty.");
    return false;
  }

  if (user.getPassword().length() < 8) {
    System.out.println("Password must be at least 8 characters long.");
    return false;
  }

  return true;
}

validateUser()的重构版本如下。

public boolean validateUser(User user) {
  if (!hasValidName(user)) { // name validation details abstracted
    logError("Invalid name"); // logging details abstracted
    return false;
  }

  if (!hasValidEmail(user)) { // email validation details abstracted
    logError("Invalid email"); // logging details abstracted
    return false;
  }

  if (!hasValidPassword(user)) { // şifre validation details abstracted
    logError("Invalid password"); // logging details abstracted
    return false;
  }

  return true
}

即使代码的数量随着新功能而增加,请注意阅读的容易以及维护的容易程度。例如,想象一下您正在检查验证阶段的年龄验证:

  • 您检查了功能。
  • 您很快就会看到验证步骤,并注意到缺少年龄验证。
  • 如有必要,您可以创建一个验证功能以进行年龄验证并将其集成到代码中。

完成了。由于抽象非常清晰,因此您无需浏览数百行代码即可查看哪种代码通过提取密码和电子邮件验证详细信息来验证年龄。

实践

假设您的功能可以计算产品列表的总价格,并花一分钟的时间来分析其违反SLA原理的原因。尝试按照的原则进行重构

public int calculateTotalCartPrice(CartItem[] cartItems) {
  int totalPrice = 0;

  for (CartItem cartItem : cartItems) {
    totalPrice += cartItem.getPrice();

    if (cartItem.isTaxable()) {
      totalPrice += cartItem.getPrice() * 0.18;
    }

    if (cartItem.getCategory().equals("Electronics")) {
      if (cartItem.getPrice() > 500) {
        totalPrice -= 50;
      }
    }
  }

  return totalPrice;
}

当我们检查功能时,我们可以看到它将产品价格添加到总金额中,将产品税添加到其中,并在可用时最终扣除折扣金额。我们可以将代码分为三个部分。让我们逐步进行重构:

  • 计算calculateTotalCartPrice()中循环中产品价格的逻辑与其他代码的抽象水平不同。让我们将其提取到单独的功能中。

如果一个函数包含一个循环,则可能包含违反单个抽象级别的代码。

public int calculateTotalCartPrice(CartItem[] cartItems) {
  int totalPrice = 0;

  for (CartItem cartItem : cartItems) {
    totalPrice += calculateCartItemPrice(cartItem);
  }

  return totalPrice;
}

private int calculateCartItemPrice(CartItem cartItem) {
  int cartItemPrice = cartItem.getPrice();       // abstraction level 1

  if (cartItem.isTaxable()) {
    cartItemPrice += cartItem.getPrice() * 0.18; // abstraction level 2
  }

  if (cartItem.getCategory().equals("Electronics")) { 
    if (cartItem.getPrice() > 500) {             // abstraction level 2
      cartItemPrice -= 50;
    }
  }

  return cartItemPrice;
}
  • 我们已经成功重构了calculateTotalCartPrice()函数,但是新功能仍然具有不同的抽象水平。此功能的主要行动是返回产品价格,但税收和折扣的计算是子行动。

如果您在向代码发音时使用动词,则需要提取一块代码。让我们看看上面的示例:

  • 获取产品价格(此操作已经是创建此功能的原因)
  • 计算税(必须提取起功能)
  • 计算折扣(必须提取到功能)
  • 返回最终价格(此操作已经是创建此功能的原因)
public int calculateTotalCartPrice(CartItem[] cartItems) {
  int totalPrice = 0;

  for (CartItem cartItem : cartItems) {
    totalPrice += calculateCartItemPrice(cartItem);
  }

  return totalPrice;
}

private int calculateCartItemPrice(CartItem cartItem) {
  int cartItemPrice = cartItem.getPrice();

  if (cartItem.isTaxable()) {
    cartItemPrice += calculateTax(cartItem.getPrice());
  }

  if (isElectronicItem(cartItem)) {
    cartItemPrice -= calculateDiscount(cartItem.getPrice());
  }

  return cartItemPrice;
}

private int isElectronicItem(CartItem cartItem) {
  return cartItem.getCategory().equals("Electronics");
}

private int calculateTax(int price) {
  return price * 0.18;
}

private int calculateDiscount(int price) {
  return price > 500 ? 50 : 0;
}

由于重构的结果,我们得到了一个干净的代码和子孙后代的祝福。


我们想以最小化错误的方式编写代码,并且将来很容易弯曲。 单一级别抽象之类的原则也通过完善细节来鼓励编写干净的代码。

保持健康。