装饰器、属性、切面和特质之间有什么区别?

22

就纯计算机科学(或者计算语言学)的角度而言,我想了解以下单词之间的区别:

  • 装饰器
  • 属性
  • 方面
  • 特征

不同的编程语言以不同的方式使用这些单词和功能。例如,在Python中,装饰器(根据Python Wiki)会动态地改变函数、方法或类的功能,而不需要直接使用子类或更改被装饰的函数的源代码。

这让我想起了面向方面编程工具,比如PostSharp或DynamicProxy。也就是说:

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

来源:PostSharp 示例

在 C# 和 Java(以及众多其他语言)中,属性可以表示装饰器模式(C#),也可以表示字段(Java)。

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

所以,从“纯”的角度来看,这些概念的规范定义是什么?有没有更好的定义方式?


不同编程语言之间没有标准。此外,我认为并不是所有的语言都支持每个严格定义。以Java为例,装饰器=注释,属性=字段,Trait=默认方法(也许是接口)...而方面则更多地是一个软件架构概念,而不是任何一种语言的特性。 - OneCricketeer
1
因此,每个相关的“事物”都应该有一个范式级别的“术语”。从理论角度来看,我很难相信你不能更准确地量化它们。 - Darkenor
2
我不确定那与此相关。问题涉及理论和计算机科学。但答案通常是肯定的。我处理许多不同技术上的事情。 - Darkenor
我认为这个问题非常有价值。无论是计算机语言还是自然语言,只要人们了解语言的演变,就可以更容易地克服语义上的误解。这使得寻找共同概念和识别思维差异变得更加容易。 - Michael Scheper
1
@MichaelScheper 注解不会直接修改行为或添加新的行为,而装饰器会。注解可能会间接地改变行为,但注解基本上只是关于类的元数据,额外的信息。 - Tim
显示剩余2条评论
1个回答

20

装饰器

在IT技术中,我认为装饰器是指设计模式中的一种。这种模式被广泛地应用于许多语言中,特别是面向对象的语言。因此,装饰器作为一种模式,是一个包装器,可以为被装饰的函数或类添加新的功能。

我能想到的最简单的例子是一个装饰器函数。该函数会接收另一个函数作为参数,并返回一个新的函数,这个新的函数可以对原来的函数进行一些额外的操作。

int foo(int x)

可以通过另一个接受第二个参数的函数对其进行装饰,该函数执行其他操作,然后反过来调用foo(),传递原始参数x。

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

虽然设计模式通常应用于类级别,但这里的原则是相同的,我认为它很好地说明了装饰器的含义。每种语言是否都遵循这一点是另一回事。一种语言可能有其他称之为“装饰器”的东西,但对我来说,这个概念最符合在不改变原始代码甚至不使用继承的情况下,将某些普通的东西用额外的功能装饰起来。

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

OutputStream s

然后,您可以使用更专业的类来装饰它,具体取决于正在处理的数据类型或您希望读取数据的格式:

OutputStream s1 = new FileOutputStream("somefile.txt");
或者
OutputStream s2 = new ByteOutputStream("rawdata.hex");

属性

我倾向于认为,C#中的“attribute”是正确的理解,因为它与装饰器不同。属性赋予一个语义值,这个值可以在不同的用途之间甚至在使用相同属性的API之间发生变化。例如,我可能有两个函数:

[Logging] private void a() { ... }
[Security] private void b() { ... }
我可以为一个分配一个日志属性,为另一个分配安全属性,而这些属性的含义对检查这些属性的客户端 API 可能有所不同。可能会使用 log4j 来实现日志记录,也可能会使用其他 API。这里的定义更加流动和开放,可以由我的代码的不同参与方或用户进行解释。某个属性肯定可以用作装饰器,但是属性可用途远不止于此。
仅作澄清,属性一词也用于表示类的成员变量。在这里,我们谈论的是将预定义语义值分配给现有对象或类的更大、更抽象的概念。Java 将其称为注释。
我心目中对它成为属性(就我们所谈论的意义而言)的一个限定因素是它不直接修改行为,只是间接地影响。例如,将 [Logging] 属性分配给某个东西并不会以任何方式改变它的代码。这就像附上一个名牌,其他人正在寻找它。当另一个程序或应用程序看到名牌时,它会推断出某些事情,并可能相应地更改其行为。但是(至少在 Java 中),注释或属性不会直接修改任何内容——只是一个名牌。在支持属性的 C# 或其他语言中,情况可能略有不同,在这种情况下,我会认为它们是更高级的属性或完全不同的东西。
在面向方面编程(AOP)的意义上,Aspect 是一种自修改或自更新的代码构造。它将某个代码部分定义为更具可塑性(切入点),并允许该特定部分在一个或多个可能的更新、补丁或相同代码部分的不同版本之间进行交换。
使用 Aspect 可以像装饰器和属性一样执行一些操作吗?当然可以。但是为什么要给自己制造麻烦呢?AOP 就像从 OOP 迈向的下一步,只有在必要时才应使用它。何时才是必要的呢?当一个特定的应用程序有很多“跨领域问题”(cross-cutting concerns)时,例如安全性或日志记录——比如银行应用程序。这些关注点贯穿始终;它们超越了使良好定义的类和包变得美观的传统边界。当您在记录日志时,除非记录所有内容,否则不会有太大的好处;因此,这个关注点是跨越领域的。因此,当您要更新一个类的日志记录机制时,同时修改所有其他类和 API 会很困难,但也是必要的。否则,您的记录日志现在就不一致和混乱,更难以用于故障排除或监视。
为了使这些更新变得不那么麻烦,引入了面向方面的语言,如 AspectJ。我还没有遇到过除装饰器之外任何其他含义的“方面”(aspect),但是可能会有一些,正如前面关于装饰器所述的那样。某种语言可能将某些内容称为“Aspect”,但它可能看起来更像我们已经讨论过的其他东西之一。
Trait 与接口同义,至少在我学习的语言中是这样。接口是一个 OOP 概念,它声明行为而不实现它们。创建这些期望的行为允许这些行为变得通用,并
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()函数,我就可以放心地得到正确的结果。每个子类知道自己该做什么。在C ++中,情况要复杂一些,但总体概念是相同的(如果您想深入了解,请阅读虚拟关键字)。

总结

我希望这能解决一些困惑。正如你所说,许多不同的语言对每个词汇都有很多不同的含义,无疑会增加混淆。我相信如果你进一步研究,你会发现这些通常是标准含义。我已经在许多语言(VB,C ++,C#,Java,Paschal,PHP,Perl)中进行编程超过18年,这些定义是我最舒适的信仰之一。

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


4
在我看来,特质远不止于接口。接口只声明了可以找到哪些方法,但并没有提供实现。特质也包括定义。因此,我认为特质更像是一种可以对多个类进行部分定义的“部分类”。 - derM
1
@derM 正如我在我的回答中所述,不同的编程语言有不同的含义。我同意特质可以远远超出仅仅是一个接口的范畴。我从未说过它们不能成为更多的东西。当你将特质的本质与它们有时可能意味着的东西区分开来时,你最终会得到接口作为共同点。我同意这里存在歧义,但这就是讨论的本质。请随意发布您自己的答案,以更好地解释特质。 - Tim
请注意,.NET 属性不与 _对象_(实例)相关联,而是与类型系统实体(例如类型、字段、属性、方法和参数)相关联。我倾向于将属性视为无效的标签或标签,可以通过反射为编译器、运行时或自定义代码提供巨大的能力。 - Robert Schmidt
@RobertSchmidt 感谢您的澄清。我会进行编辑以更精确地表明这一点。 - Tim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接