简体   繁体   English

设计界面

[英]Design interfaces

I have what I believe is a fairly common problem. 我有一个我认为是相当普遍的问题。 There is an interface called IService , and some derived classes that implements this interface; 有一个称为IService的接口,还有一些实现此接口的派生类。 ServiceA, ServiceB, ServiceC and ServiceD. ServiceA,ServiceB,ServiceC和ServiceD。

ServiceA and ServiceB needs a function called getSomeType() to be exposed, but this function is not needed in the other derived classes. ServiceA和ServiceB需要getSomeType()一个名为getSomeType()的函数,但在其他派生类中则不需要此函数。 Also ServiceD needs another function exposed that the other derived classes doesn't need. 另外,ServiceD还需要其他公开类不需要的另一个函数。 How should I solve this problem? 我应该如何解决这个问题? I feel that using dynamic_cast is not the right way to do it, or is it? 我觉得使用dynamic_cast不是正确的方法,是吗? I also considered creating a new interface so that ServiceA and ServiceB implements two interfaces. 我还考虑过创建一个新接口,以便ServiceA和ServiceB实现两个接口。

class IService
{
public:
   virtual IService() {};

   virtual void start() = 0;

   virtual void stop() = 0;
};

class ServiceA : public IService
{
public:
    void start() override;

    void stop() override;

    ISomeType * getSomeType();
};

Derive IServiceAB from IService, derive ServiceA and ServiceB from IServiceAB, and derive ServiceD from IService. 从IService派生IServiceAB,从IServiceAB派生ServiceA和ServiceB,并从IService派生ServiceD。

One other way is too have a look into the decorator pattern (design patterns). 另一种方法是查看装饰器模式(设计模式)。

There most important tool in your design arsenal is a so-called 'substitution principle'. 设计库中最重要的工具是所谓的“替代原理”。 When you use public inheritance with polymorphism (btw, in C++ world we usually do not say 'implements interface', since interface is not a C++ term) you are claiming so-called IS-A principle - that is, you claim that for the outside observer ServiceA is, for all intents and purposes, IService. 当您将公共继承与多态性一起使用时(顺便说一句,在C ++的世界中,我们通常不说“实现接口”,因为接口不是C ++术语),您会声称是所谓的IS-A原理-也就是说,您声称出于所有意图和目的,外部观察者ServiceA都是IService。 No more, no less. 不多不少。 Everything which exists in IService exists in ServiceA. IService中存在的所有内容都存在于ServiceA中。 Everything (visible outside) which exists in ServiceA, exists in IService. ServiceA中存在的所有内容(外部可见)都在IService中存在。

Whenever you feel the need to do a dynamic_cast , you are clearly violating this principle - because it means that IService is no longer ServiceA, you need to specifically cast to it. 每当您觉得需要进行dynamic_cast ,您就明显违反了该原理-因为这意味着IService不再是ServiceA,因此需要专门将其强制转换。

Solution. 解。 Do not use public inheritance with polymorphism for classess which do not follow IS-A principle. 对于不遵循IS-A原理的类,请勿使用具有多态性的公共继承。 In your case, ServiceA is not IService, which means, IService is not a suitable base class for ServiceA. 在您的情况下,ServiceA 不是 IService,这意味着IService不是ServiceA的合适基类。

This is often a tricky problem. 这通常是一个棘手的问题。 If, instead of service, we say we deal with animals - because we can use that as a proxy for "things that can do some things, but not other things". 如果说不是处理服务,而是要与动物打交道-因为我们可以将其用作“可以做某些事情但不能做其他事情的事情”的代理。 Say we have the animals Fish , Bird , Cow , Dog and Cat . 假设我们有动物FishBirdCowDogCat

So, we think of things animals in general can do: 因此,我们认为动物通常可以做到:

  • Move - in different ways, eg Swim , Fly , Walk 以不同的方式移动,例如SwimFlyWalk
  • Make a noise - call this Talk 发出噪音-调用此Talk
  • Have properties such as length, number of legs, weight, etc. 具有诸如长度,腿数,重量等属性。
  • Some animals can be trained to "do stuff" (dog can fetch, a parrot can "talk" on demand), 可以训练一些动物“做东西”(狗可以取食,鹦鹉可以按需“说话”),
  • A cow can produce milk, but (for practical purposes), none of the others. 母牛可以生产牛奶,但(出于实际目的)其他都不可以。

So, we have for example Fish , which, obviously, can swim , but can't talk , which a Dog or Bird can do. 因此,例如,我们有一条Fish ,显然,它可以swim ,但不会talk ,而DogBird可以做到。 A Bird can fly but can't Swim [yes, there are excellent swimming birds, I'm simplifying reality]. Bird会飞,但不会Swim [是的,有优秀的游泳鸟,我是在简化现实]。 A Dog can make sounds, but certainly can't fly. Dog可以发出声音,但肯定不会飞。 A Cat can make sounds, swim (reluctantly), but won't be able to Fetch . Cat可以发出声音,游泳((勉强地)游泳),但无法Fetch声音。

So, it becomes a problem of how we represent this ability to do certain things, but not other things. 因此,这成为我们如何代表这种能力去做某些事情而不是其他事情的问题。

There are several solutions, but ultimately, there are two possible solutions 有几种解决方案,但最终有两种可能的解决方案

  • Dummy functions that don't do anything (eg empty function) for those functions that aren't applicable: Talk on Fish , or Swim on Bird . 虚函数,没有做任何事情(如空函数),对于那些不适用的功能: TalkFish ,或SwimBird
  • Conditional code - such as a CanSwim function, that answers whether the animal can swim or not. 条件代码-例如CanSwim函数,用于回答动物是否会游泳。 In which case the Swim function may throw an exception if it's still being called. 在这种情况下,如果Swim函数仍在被调用,则它可能会引发异常。

Ultimately, somewhere, we need to know that they are different types of animals, and that some can do some things, others can't. 最终,在某个地方,我们需要知道它们是不同类型的动物,有些可以做某些事情,有些则不能。 Either asking a bird to swim will fail (and shouldn't happen) or will "do nothing". 要求一只鸟游泳会失败(并且不应该发生),或者“什么也不做”。 You really have to determine what the right thing to do is. 您确实必须确定正确的做法是什么。

I do have this problem in my compiler project. 我的编译器项目中确实有此问题。 I have an "abstract syntax tree" that represents the source code in a parsed form. 我有一个“抽象语法树”,以解析的形式表示源代码。 So it has nodes for variable declarations, function declaratins, assignments, binary operations, while-loops, for-loops, etc, etc. These have the same basic interface, which provides a CodeGen function that actually makes the (intermediate representation) - so a binary expression of a + b will generate code to load a , load b and then add the two together. 因此,它具有用于变量声明,函数声明,赋值,二进制操作,while循环,for循环等的节点。这些节点具有相同的基本接口,该接口提供了一个CodeGen函数,该函数实际上使(中间表示)成为可能。 a a + b的二进制表达式将生成代码以加载a ,加载b ,然后将两者加在一起。 Codegen for a function will call codegen for the body of the function, etc. 一个函数的Codegen将调用该函数的主体的codegen等。

There are half a dozen other general purpose functions that apply to (almost) all AST entries. 还有六种其他通用功能适用于(几乎)所有AST条目。 But there are a few operations that only really make sense on a particular situation, for example when dealing with binary operators, strings are not supported by the intermediate representation as add for concatenation. 但是,有一些操作仅在特定情况下才真正有意义,例如,在处理二进制运算符时,中间表示形式不支持将字符串作为连接的add So can't just load, load and add - have to call the strcat function. 因此,不能只加载,加载和添加-必须调用strcat函数。 For these special cases, I use dynamic_cast to check if the, in this case, the type is matching the string type, and if so, fall into the "special string operation" code-path. 对于这些特殊情况,我使用dynamic_cast来检查类型(在这种情况下)是否与字符串类型匹配,如果匹配,则属于“特殊字符串操作”代码路径。 I could add a isString function for ALL of the types, but it would be rather awkward and not much benefit, as MOST of the time, it's not important. 我可以为所有类型添加一个isString函数,但这会很尴尬并且没有太多好处,因为在大多数情况下,这并不重要。 I don't think this is a great solution. 我认为这不是一个很好的解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM