【问题标题】:Do getters violate the Law of Demeter?吸气剂是否违反得墨忒耳定律?
【发布时间】:2023-04-04 09:40:01
【问题描述】:

想象有一个GameState 类型使用GameContext(通过process 方法):

abstract class GameState {
    public abstract void process(GameContext context);
}

GameContext 将包含诸如 Player、Shops 等对游戏至关重要的东西。

一个状态可以访问它需要的东西:

class CombatState extends GameState {
    public void process(GameContext context) {
        Player player = context.getPlayer();

        if(player.isAlive()) {
            //...
        }
    }
}

语句player.isAlive() 可以重写为context.getPlayer().isAlive()

我的问题

得墨忒耳法则规定对象只能与直系亲属互动。这是否违反了原则,如何解决?

对于要动态处理的每个状态,所有可能的状态都必须接受形式参数。 这使得严格传递对象所需的内容变得困难,这就是每个状态从“主要来源”获取所需内容的原因。我觉得主要来源的凝聚力很低,因为ShopState 需要的数据与CombatState 不同

【问题讨论】:

    标签: java getter law-of-demeter


    【解决方案1】:

    状态不是进程,进程是进程。战斗是一个过程,活着是一个状态。

    使用process 的抽象名称会让你需要打破得墨忒耳定律。

    看这个例子:

    class CombatProcess extends GameProcess {
        public void hit(Player puncher, Player beaten) {
            if (beaten.getState().isAlive()) {
               Weapon oneWeapon = one.getCurrentWeapon();
                   ...
            }
        }
    }
    

    CombatProcess 中的一切都尽可能具体。

    分析哪个玩家与哪个玩家战斗不是 CombatProcess 本身的责任!在 CombatProcess 开始之前,您必须知道与谁作战。

    编辑:

    在你写的这个答案的 cmets 中:

    如果我在 Set、List 或 Map 中有状态,我将无法多态地处理这些状态,

    这是绝对正确的。 Hook/Anchor-Pattern 是从 1996 年开始的,并且仍然在 Windows 中广泛使用和荣耀(所谓的 system-hooks)。不幸的是,由于一些批评,它没有进入 OOD 的前 10 种模式。其中一位批评者是:

    ...把一个进程的所有操作抽象成逻辑独立的钩子,然后锚定它们并适当地迭代它们并不容易。

    我个人的看法是,Hook/Ancher-Pattern 在 1996 年是革命性的,未来与 CDI(例如 spring)的结合将是革命性的。

    终于!你可以决定:

    1. 打破得墨忒耳法则。
    2. 放下钩子/锚模式。
    3. 写一个解决方法,描述here

    【讨论】:

    • 喜欢你如何直接解决命名问题。为什么命名事物是计算机科学中的两个难题之一的一个很好的例子。
    • 我已经提到改变GameState#process(GameContext)的形参:“对于每个动态处理的状态,形参必须被所有可能的状态所接受。这使得很难严格传递对象它需要的东西,这就是为什么每个状态都从“主要来源”“获取它需要的东西 - 如果我有SetListMap中的状态,我会无法多态处理状态,因为每个状态都有不同的要求。
    • 在你的方法中,你有oneWeapon = one.getCurrentWeapon()。如果我要拨打oneWeapon.doSomething() 之类的电话怎么办?会和one.getCurrentWeapon().doSomething()一样,违反原则吧?
    • 我也不认为State#process 是一种错误的命名约定。 GameState 可以实现某种Processable<T>,这将使其合理。需要以some 方式处理状态。按照您建议的方式进行操作(更改形式参数)会引入更多管理,因为每个状态都需要由某个容器显式处理(以便传入正确的参数值)。如果有 5000 个州,某种StateTransitionTable 就会失控
    • Peter Rader 在这种情况下您的解决方案是什么:stackoverflow.com/q/37916206/6053907 ?
    【解决方案2】:

    由于定界符定律是松散耦合的一个特殊情况。

    我认为它的工作方式是:

    即使我们链接了多个方法(比如),我们也可以访问特定类的字段及其方法。我们在java中一直这样做,需要注意的是,链接仅适用于单个类的方法。

    分隔符法则告诫我们不要同时访问多个类的对象和/或方法

    例子:

    如果我们有 A、B、C 类。

    B 类有一个字段持有 A 的引用,C 有 B 的引用。

    那么我们不应该使用 getter 来获取 A 到 C 的引用,它有助于维护松散耦合。

    再说一次,这就是我认为它的工作方式。

    【讨论】:

    • 链接仅适用于单个类中的方法”是什么意思?你是说如果它来自同一个实例,例如context.getPlayer().doSomething(),链接是可以接受的?如果是这样,那实际上就是得墨忒耳定律所要避免的。这将与GameContext(间接亲属)中包含的Player 实例交互,而不是仅与GameContext 交互。传入Player 而不是GameContext 会很好,但这意味着每个州都需要自己独特的形式参数
    • @VinceEmigh 不,你错了,我的建议是:假设你有一个字符串引用's',那么 s.replaceFirst().replaceAll() 是有效的(因为引用和方法属于同一类)
    • 将问题移至programmers.stackexchange.com,您将成为我所有的支持者,而我没有:D。
    • @PeterRader 您的回答确实解释了 OOP 的一个重要概念,但没有解释分隔符定律,不过,我相信我的支持并没有白费。 ;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-30
    • 2011-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多