C++ 软件设计:课程太多?

C++ 软件设计:课程太多?,c++,oop,design-patterns,C++,Oop,Design Patterns,就软件设计而言,我是个外行。我正面临一个“问题”,可能是通过一些我想被告知的众所周知的技术/习惯用法/模式来解决的 我有一个抽象的基类,基本上定义了一个纯虚成员函数和其他一些函数。然后,我有几个类从这个类派生出来,并覆盖了所说的虚函数。我现在有六门这样的课程,而且数量还在增加。这些类只有几个数据成员(非常少,比如两个double或加上一个函数指针),它们的区别主要在于它们执行非常短的计算的方式。我想知道这是否意味着一个糟糕的设计,以其他方式处理是否更好 如果合适的话,有人能告诉我应该知道的相关设

就软件设计而言,我是个外行。我正面临一个“问题”,可能是通过一些我想被告知的众所周知的技术/习惯用法/模式来解决的

我有一个抽象的基类,基本上定义了一个纯虚成员函数和其他一些函数。然后,我有几个类从这个类派生出来,并覆盖了所说的虚函数。我现在有六门这样的课程,而且数量还在增加。这些类只有几个数据成员(非常少,比如两个double或加上一个函数指针),它们的区别主要在于它们执行非常短的计算的方式。我想知道这是否意味着一个糟糕的设计,以其他方式处理是否更好

如果合适的话,有人能告诉我应该知道的相关设计模式或习惯用法吗。谢谢

编辑


为了澄清问题,抽象基类没有任何数据成员。并非所有派生类都有数据成员。我要做的是将积分的坐标变换作为类。给定的转换只需要几个参数,有时还需要用户提供的函数

俗话说:干DRepeatY我们自己。除了重写纯虚拟方法之外,如果存在任何其他形式的重复代码,那么这可能是需要修改的迹象。如果您有很多类,并且每个类在功能上都是唯一的,那么这没关系。

如果可以组合操作,那么您应该尝试组合对象以组合操作。一个类应该只做一件事,所以你不一定会有问题。

如果不看一个例子,很难完整地进行评论。通常,您有许多类,每个类只执行一小部分(一组)操作,这一事实并不一定表明设计不好。您应该尽可能使用DRY(不要重复您自己)和SOLID(请参阅)原则构建类。

如果您的抽象基类没有任何数据成员(如果它有一个纯虚拟方法,似乎不应该),那么确实有一个更好的模式。假设我们有以下代码:

struct AbstractBase {
  virtual double calc(double) = 0;
  virtual ~AbstractBase() = default
}
现在,为了在其他地方动态使用东西,您必须从中继承:

struct Derived : public AbstractBase { ... }

void BaseUser(AbstractBase& ab) { ... };
一个耦合性较低的解决方案就是将类作为函数对象编写,并使用
std::function

struct Derived {
  double operator()(double x) { ... };
}

void User(std::function<double(double)> f);

User(Derived{}); // Calls user with Derived routine.

如果你需要比一个抽象的基类更精确的控制,只有一个虚函数调用是可以的,但是当接口中只有一个函数时,我至少应该考虑函数对象。


我不关心您必须拥有的派生对象的数量。

带有一个虚拟函数的抽象基类听起来几乎完全像包含lambda的
std::function

例如:

#include <functional>
#include <vector>
#include <iostream>

int main()
{
    using op = std::function<int()>;

    int x = 7;
    int y = 5;
    auto a = [x, y]() -> int { return x + y; };
    auto b = [x, y]() -> int { return x - y; };

    auto ops = std::vector<op> { a, b };
    for (const auto& o : ops)
    {
        std::cout << o() << std::endl;
    }
}
#包括
#包括
#包括
int main()
{
使用op=std::函数;
int x=7;
int y=5;
自动a=[x,y]()->int{returnx+y;};
自动b=[x,y]()->int{returnx-y;};
自动操作=标准::向量{a,b};
用于(常数自动和o:ops)
{

std::cout如果代码没有被复制,那么你就在正确的轨道上。抽象基类有任何数据成员吗?同意@Dialogicus,但函数指针引起了我的注意。为什么是函数指针?你不能将其设计成给定子类的细节吗?顺便说一句,如果你还没有,它可以简化乐趣的使用Action指针。在O-O编程中看到这一点是完全可以接受的,也是应该看到的。但与此同时,一些人(比如我)发现它不必要地冗长,甚至令人困惑——更不用说有时很难导航和维护——特别是当你出于某种原因(比如一些设计模式)需要一个并行类层次结构时。还有其他选择,例如,数据驱动设计,您可以将所有内容打包得更紧凑(语法方面)进入表格组织-如果所有的子类都足够统一,并且类层次结构很浅,这是合适的。还有其他方法。模板可以非常好。我通过添加一些模板和lambda magic在一个项目中删除了60个类。我会改写一下。一个类应该代表一件事。它可以做很多事情即使你的函数需要状态,你也可以把它们写成lambda,在构建它们时从上下文中获取它们需要的东西,然后把它们作为函数传递……或者作为lambda!是的,抽象基类没有任何数据成员。而且,并不是所有的派生类都有成员。我正在做的实际工作是使用坐标转换作为类的积分的形式。一些可能会从用户处收到函数。我将编辑OP以澄清这一点。@davidbak是的,但如果OP的抽象基类有状态,那么就不可能用这种设计进行模拟。我的评论是专门针对基类中的状态,而不是实现中的状态。@booNlatoT如果不是一个ll派生成员实际上有数据,那么IMHO这是这样做而不是使用虚拟继承的有力理由。对于无状态函数来说,实际函数比编写函子更清晰。我已经尝试过了。std::function plus lambdas似乎非常适合我所拥有的。谢谢!如果您坚持使用,您甚至不需要std::function
auto
decltype
您可以使用“裸体”lambdas,据我所知,如果您不需要提供的通用性,它可以比std::function更紧凑、更高效。@davidbak我假设OP需要一个多态接口,因为他使用的是ABC。没有示例代码,就不可能知道。是的,示例代码会很好。我明白您所说的polymorphic接口。谢谢您的参考。
#include <functional>
#include <vector>
#include <iostream>

int main()
{
    using op = std::function<int()>;

    int x = 7;
    int y = 5;
    auto a = [x, y]() -> int { return x + y; };
    auto b = [x, y]() -> int { return x - y; };

    auto ops = std::vector<op> { a, b };
    for (const auto& o : ops)
    {
        std::cout << o() << std::endl;
    }
}