Decorator、Attribute、Aspect 和 Trait 之间有什么区别?

What is the difference between a Decorator, Attribute, Aspect, and Trait?

从纯计算机科学(或者计算语言学)的角度,我想知道这两个词的区别:

各种语言以不同的方式使用这些词和功能。例如,在 Python 中,装饰器 [根据 Python Wiki](强调我的):

Decorators dynamically alter the functionality of a function, method, or class without having to directly use subclasses or change the source code of the function being decorated.

这让我印象深刻,因为它与 PostSharp 或 DynamicProxy 等面向方面的编程工具非常相似。即:

   [Profile]
   private static void SleepSync()
   {
       Thread.Sleep(200);
   }

来源:PostSharp Examples

在 C# 和 Java(以及无数其他语言)中,属性可以表示 Decorator-ish 模式 (C#) 或字段 (Java)。

而在 C++ 中,通过 boost 或 PhP 通过内置的 trait 词,我们可以使用 traits 来扩展 类,如下所示:https://en.wikipedia.org/wiki/Trait_(computer_programming)

那么,从 "pure" 的角度来看,所有这些实际上是什么的规范定义是什么?有没有更好的方式来定义它们?

装饰器

我从设计模式的角度想到了Decorator。设计模式在许多语言中得到认可,尤其是面向对象的语言。装饰器作为一种模式,然后是一个包装器,它添加了函数中不存在的功能或 class 被装饰。

我能想到的最简单的例子就是装饰函数。函数

int foo(int x)

可以被另一个接受第二个参数的函数修饰,用它做一些其他事情,然后依次调用 foo(),传递原始参数 x。

int bar(int x, int y) {
    return y*y + foo(x);
}

虽然设计模式通常应用于 class 级别,但这里的原理是相同的,我认为它很好地说明了装饰器的含义。每种特定语言是否遵守这一点是另一回事。一种语言可能有其他东西,它称之为 'decorator' - 但对我来说,这个概念最符合用附加功能装饰一些简单的东西的想法,而不改变原始代码甚至不使用继承。

另一个常见的例子是 Java 中的 I/O classes。有基础的

OutputStream s

然后您可以根据正在处理的数据类型或您希望读取数据的格式,使用更专业的 classes 对其进行修饰:

OutputStream s1 = new FileOutputStream("somefile.txt");

OutputStream s2 = new ByteOutputStream("rawdata.hex");

属性

我倾向于 C# 属性的想法是正确的理解,因为它不同于装饰器。一个属性分配一个语义值,它可以从一种用途到另一种用途而变化,甚至在使用相同属性的 API 之间也是如此。例如,我可能有两个函数:

[Logging] private void a() { ... }
[Security] private void b() { ... }

我可以分配一个日志记录属性和一个安全属性,这些属性的含义可能与检查这些属性的客户端 API 不同。可以使用 log4j 来实现日志记录,也可以使用另一种 API。这里的定义更加流畅,并且可以由我的代码的不同方或用户进行解释。当然可以使用属性作为装饰器,但属性的用途远不止于此。

为了消除歧义,词属性也用来表示class的成员变量。这里我们讨论的是更大、更抽象的概念,即为现有对象或 class 分配预定义的语义值。 Java 调用这些注释。

我认为它是一个属性,在我们谈论的意义上,一个限定符是它不直接修改行为,只是间接修改。例如,将 [Logging] 属性分配给某物不会以任何方式更改其代码。这就像附上别人正在寻找的名称标签。当另一个程序或应用程序看到名称标签时,它会推断出某些事情并可能相应地改变其行为。但是(至少在 Java 中)注释或属性不会直接修改任何东西——同样,只是一个名称标签。这在 C# 或其他支持属性的语言中可能略有不同,在这种情况下,我会认为它们是更高级的属性或完全不同的东西。

看点

面向方面编程 (AOP) 意义上的方面是一种自我修改或自我更新的代码构造。它将一段代码定义为更具延展性(切点),并允许在一个或多个可能的更新、补丁或同一段代码的不同版本之间交换该特定部分。

你能做一些与使用方面的装饰器和属性相同的事情吗?当然。但是你为什么要让自己头疼呢? AOP 就像 OOP 的下一步,只有在必要时才应该使用它。什么时候需要?当特定应用程序有很多 "cross-cutting concerns" 时,例如安全或日志记录 - 例如,银行应用程序。这些问题是交叉的;它们超越了传统的界限,使 class 的定义和包很好。当您进行日志记录时,除非您记录所有内容,否则它并没有多大用处;因此,这一关切是贯穿各领域的。因此,当您去更新一个 class 的日志记录机制时,很难同时修改所有其他 class 和 API,但这也是必要的。否则,您的日志记录现在会不一致且令人困惑,并且更难用于故障排除或监控。

为了减少这类更新的麻烦,引入了面向方面的语言,如 AspectJ。我没有 运行 跨越 "aspect" 这意味着除此之外的任何东西,但可能有一些如前所述关于装饰器。一种特定的语言可能称某些事物为方面,但它可能看起来更像是我们已经讨论过的其他事物之一。

特质

特征是接口的同义词,或者至少在我研究过的语言中它是这样的。

接口是一个 OOP 概念,它声明行为而不实现它们。创建这些预期的行为可以让这些行为变得通用,并且可以在不关心细节的情况下被通用地调用。留给 subclasses 来实现它们。

classic OOP 示例是 Animal,有两个子classes,Cat 和 Dog。

public interface/trait Animal {
    public void speak();
}
public class Cat implements Animal {
    public void speak() {
        output("Meow.");
    }
}
public class Dog implements Animal {
    public void speak() {
         output("Bark!");
    }
}

这也是多态性的一个很好的例子 - 那些往往让非计算机人员畏缩的词之一。只是说猫和狗有个体行为,如果我声明一个Animal对象,我不管你给我什么样的。你可以给我一只猫或一只狗。因为两者都是动物,在任何一种情况下,我所要做的就是调用我的 Animal 对象的 speak() 函数,我可以放心,在任何一种情况下都会出现正确的结果。每个单独的 subclass 都知道它需要做什么。在 C++ 中,这里的水有点浑浊,但一般概念是相同的(如果你想从那个兔子洞开始,请阅读关键字 'virtual')。

总结

我希望这能消除一些困惑。正如您所说,许多不同的语言似乎对其中的每一种都有许多不同的含义,这无疑造成了混乱。我相信,如果你进一步研究,你会发现,大体上,这些是标准的含义。我已经使用多种语言(VB、C++、C#、Java、Paschal、PHP、Perl)编程 18 年以上,这些是我最相信的定义一种标准。

我当然欢迎就我所说的进一步讨论。