【问题标题】:Too Many If Statements Inside Factory工厂内部的 If 语句过多
【发布时间】:2016-01-28 18:02:23
【问题描述】:

我目前正在开发具有许多不同设计模式的应用程序。它需要遵循良好的实践,基本上没有代码异味。

我正在使用工厂方法打印出随机类型的对象,但我必须使用 3 个 if 语句,这似乎效率低下……如果我想打印出 10 个不同的对象会怎样?如果没有其他方法可以解决此问题,是否只需要添加更多语句。

** 这个特定方法在工厂中的最终用途是只返回一个球类型的随机对象 (1)。

RandomGenerator ranGen = new RandomGenerator();
int randomNumber = ranGen.createRandomNumber(1,3);
if(randomNumber == 1){
     //return smallBall
}
else if(randomNumber ==2){
    //return mediumBall
}
else if(randomNumber == 3){
    //return largeBall
}

【问题讨论】:

  • 一个开关也要重复几次?
  • 您还可以创建一个包含不同球的数组并使用随机数作为索引。
  • 你的 Ball 是什么类型的数据?
  • 基于数组/映射的解决方案假定返回单个实例是可以的;例如,如果您正在生成一个充满随机球的房间,那么您可能每次都需要一个 new 实例,这需要对目前所呈现的内容进行修改。由于我们不知道这家工厂的最终用途,因此很难提供具体的答案。
  • 是多个唯一实例,每个对象具有多个不同的属性,

标签: java c++ refactoring factory


【解决方案1】:

另一种方法是应用prototype pattern

在以下示例中,我们有一个名为 RandomBallFactory 的类 通过克隆注册的原型创建随机(唯一)Ball 实例。

优点:

  • 我们可以添加新的Ball 子类,而无需更改RandomBallFactory 实现。
  • 我们可以创建相同类型但具有不同参数的对象。
  • 我们没有 if 语句。

Java 示例:

import java.util.*;

abstract class Ball implements Cloneable {
    abstract String getName();
    public Ball clone() {
        Ball ball;
        try {
            ball = (Ball)super.clone();
        } catch (CloneNotSupportedException e) {
            ball = null;
        }
        return ball;
    }
}

class SmallBall extends Ball {
    public String getName() { return "smallBall"; }
}

class MediumBall extends Ball {
    public String getName() { return "mediumBall"; }
}

class LargeBall extends Ball {
    public String getName() { return "largeBall"; }
}

class RandomBallFactory {
    private final List<Ball> prototypes;

    public RandomBallFactory() {
        prototypes = new ArrayList<Ball>();
    }

    public void registerBall(Ball ball) {
        prototypes.add(ball);
    }

    public Ball createBall() {
        Random randomGenerator = new Random();
        Integer randomNumber = randomGenerator.nextInt(prototypes.size());
        return prototypes.get(randomNumber).clone();
    }
}

public class TestBalls {
    public static void main(String[] args) {
        RandomBallFactory randomBallFactory = new RandomBallFactory();
        randomBallFactory.registerBall(new SmallBall());
        randomBallFactory.registerBall(new MediumBall());
        randomBallFactory.registerBall(new LargeBall());

        Ball ball = randomBallFactory.createBall();
        System.out.println(ball.getName());
    }
}

C++ 示例:

#include <iostream>
#include <vector>
#include <memory>
#include <cstdlib>
#include <ctime>

class Ball {
public:
    Ball() { std::cout << __func__ << std::endl; }
    Ball(Ball& other) { std::cout << __func__ << " copy from " << other.getName() << std::endl; }
    virtual ~Ball() { std::cout << __func__  << std::endl; }
    virtual std::string getName() = 0;
    virtual Ball* clone() = 0;
};

class SmallBall : public Ball {
public:
    std::string getName() { return "smallBall"; }
    Ball* clone() { return new SmallBall(*this); }
};

class MediumBall : public Ball {
public:
    std::string getName() { return "mediumBall"; }
    Ball* clone() { return new MediumBall(*this); }
};

class LargeBall : public Ball {
public:
    std::string getName() { return "largeBall"; }
    Ball* clone() { return new LargeBall(*this); }
};

class RandomBallFactory {
private:
    std::vector<std::shared_ptr<Ball> > prototypes;

public:
    void registerBall(std::shared_ptr<Ball> ball_ptr) {
        prototypes.push_back(ball_ptr);
    }

    std::shared_ptr<Ball> createBall() {
        int randomNumber = std::rand() % prototypes.size();
        return std::shared_ptr<Ball>(prototypes.at(randomNumber)->clone());
    }
};

int main(void) {
    std::srand(std::time(0));
    RandomBallFactory randomBallFactory;

    std::shared_ptr<Ball> sb_ptr(std::make_shared<SmallBall>());
    std::shared_ptr<Ball> mb_ptr(std::make_shared<MediumBall>());
    std::shared_ptr<Ball> lb_ptr(std::make_shared<LargeBall>());

    randomBallFactory.registerBall(sb_ptr);
    randomBallFactory.registerBall(mb_ptr);
    randomBallFactory.registerBall(lb_ptr);

    std::shared_ptr<Ball> ball_ptr(randomBallFactory.createBall());
    std::cout << "random Ball is: " << ball_ptr->getName() << std::endl;
}

【讨论】:

  • 看看这是用 Java 写的,我能理解你从这里来的地方。但我在 c++ 中这样做,我不确定用 etc 替换可克隆的内容
  • @hat_to_the_back 您在寻找 C++ 解决方案吗?你是认真的吗?你为什么不说这个问题?为什么这个问题用 Java 标记?为什么您的示例代码不在 C++ 中?
【解决方案2】:

您可以使用Map,类似这样(假设SmallBall 和其他人是Ball 的子类):

Map<Integer, Ball> balls = new HashMap<Integer, Ball>();

balls.put(1, new SmallBall());
balls.put(2, new MediumBall());
balls.put(3, new LargeBall());

RandomGenerator ranGen = new RandomGenerator();
Integer randomNumber = ranGen.createRandomNumber(1, balls.size());

return balls.get(randomNumber);

注意:在这个例子中,工厂方法总是返回一个对 三个实例之一,不会创建新对象。

如果您想要多个唯一实例,请将具体的 Ball-factories 放入地图中:

Map<Integer, BallFactory> ballFactories = new HashMap<Integer, BallFactory>();

ballFactories.put(1, new SmallBallFactory());
ballFactories.put(2, new MediumBallFactory());
ballFactories.put(3, new LargeBallFactory());

RandomGenerator ranGen = new RandomGenerator();
Integer randomNumber = ranGen.createRandomNumber(1, balls.size());

return ballFactories.get(randomNumber).createBall();

【讨论】:

    【解决方案3】:

    您至少有两种可能的技术可用于随机生成对象,而无需硬编码一组固定的替代方案:

    1. 构造函数/工厂方法参数的随机化,以及
    2. 使用从工厂维护的此类对象集合中随机选择的构建器对象。

    我将专注于后者。从预先构建的集合中返回随机元素的建议是一种特殊情况,其中构建器对象通常将自己作为生成的对象提供。更一般的形式可能类似于:

    interface Builder<T> {
        T createObject();
    }
    
    class Factory<T> {
        private final List<Builder<? extends T>> builders = new ArrayList<>();
        private final RandomGenerator ranGen = new RandomGenerator();
    
        T createRandomObject() {
            int randomNumber = ranGen.createRandomNumber(0, builders.size() - 1);
    
            return builders.get(randomNumber).createObject();
        }
    
        // Not shown: mechanisms for managing the available Builder objects
    }
    

    【讨论】:

      【解决方案4】:

      最简单的解决方案是使用switch 语句,例如:

      int randomNumber = ranGen.createRandomNumber(1,3);
      switch (randomNumber) {
          case 1:
              // return smallBall
              break;
          case 2:
              // return mediumBall
              break;
          case 3:
              // return largeBall
              break;
          default:
              // handle non-expected value
              break;
      }
      

      【讨论】:

      • 虽然用 N 个案例替换 switch 语句来代替 N 个 if / else if 块回答了提出的字面问题,但它并没有解决 OP 解决的可扩展性和可维护性的潜在问题暗示。
      • 是的,这是正确的。如果我有 20 个相同类型的不同对象怎么办?
      • @hat_to_the_back 然后这取决于实际用例是什么。不同的对象是否有不同的属性集(例如,您可以传递给构造函数)?等等。
      • 参考上面的例子,每个球都有相同的属性,例如大小、弹力、颜色......但是这些会有所不同,例如一个会比另一个大
      猜你喜欢
      • 2016-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-19
      • 1970-01-01
      • 2018-11-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多