将消费者作为制定者和供应商用作Java中的吸气剂是不好的做法吗?

时间:2016-11-11 21:36:07

标签: java encapsulation access-modifiers visitor

我有一个Java类,它有一些私有变量,我不打算创建setter和getter;我希望这些变量无法访问。但是有一个类需要访问这些变量。这个类是不同包中的访问者(我更喜欢将它保存在不同的包中)。允许此类为访问者提供充当setter和getter的消费者和供应商,以便访问者可以读取和修改这些变量,这是不好的做法吗?如果是,请说明原因。

示例:

A.java

public class A {
    private int x;
    private Consumer<Integer> setter;
    private Supplier<Integer> getter;
    public A(int v) {
        x = v;
        setter = new Consumer<Integer>() {
            @Override
            public void accept(Integer t) {
                x = t;
            }
        };

        getter = new Supplier<Integer>() {
            @Override
            public Integer get() {
                return x;
            }
        };
    }

    public void accept(SomeVisitor visitor) {
        visitor.setSetter(setter);
        visitor.setGetter(getter);
        visitor.visit(this);
    }
}

SomeVisitor.java

public class SomeVisitor extends ParentVisitor {
    private Consumer<Integer> setter;
    private Supplier<Integer> getter;

    public SomeVisitor() {
        setter = null;
        getter = null;
    }

    public void setSetter(Consumer<Integer> setter) {
        this.setter = setter;
    }

    public void setGetter(Supplier<Integer> getter) {
        this.getter = getter;
    }

    @Override
    public void visit(A a) {
        // Code that will, possibly, read and modify A.x
        ...
    }
}

这样,除了访问者之外,每个类都无法访问变量A.x.

更多详情:

我有一些会利用访客的课程。这些类具有彼此依赖的私有变量。如果这些变量具有设定者,则当用户更改这些变量时,可能会出现不一致,这些变量应相互依赖,而不考虑这些变量。

其中一些变量将具有getter,而其他变量则不会,因为它们仅在内部使用,不应在其他地方访问。访问者是一个例外并且应该获得对这些变量的读/写访问权限的原因是访问者要实现的功能意味着要在这些类中的方法中实现。但我认为如果我使用访客会更干净。这些功能确实需要对这些变量进行读/写访问。

这种方法背后的意图是模仿C ++中的朋友特征。我可以将访问者放在与这些类​​相同的包中(如果我找不到这个问题的简洁解决方案,我会这样做);但是我认为如果有访客也会看起来很混乱(而且会有很多访客)。

访问者将实现的功能也将与这些类关系相互关联。

2 个答案:

答案 0 :(得分:1)

我试图将其简化为评论,因为它在技术上并没有回答关于这是否是一个&#34; Bad Practice™&#34;的问题,但是这个术语很难定义,因此,无论如何都几乎不可能给出答案......

这最终似乎归结为如何Make java methods visible to only specific classes(以及类似的问题)的问题。 getter / setter只能用于一个特定的类 - 即访问者。

您在问题中使用了非常通用的名称和描述,并且很难说这一般是否有意义。

但有些要点需要考虑:

  • 有人可能会说这会破坏整体的封装。每个人都可以编写这样的访问者并获得访问get / set方法的权限。即使这将是一个荒谬的黑客:如果人们想要实现目标,他们会做那样的事情!(在下面的附录1中讨论)

  • 更一般地说,有人可能会争辩说:为什么只是允许访问setter / getter的访问者,而其他类不是?

  • 隐藏Supplier / Consumer个实例背后的getter / setter方法的一个令人信服的理由可能与可见性和类的具体性相关(详见附录2) )。但由于访问者总是依赖于访问过的类,因此这不是直接适用的。

  • 有人可能会说这种方法更容易出错。想象一下,setter或getter是null,或者它们属于不同的实例。调试这可能非常困难。

  • 正如评论和其他答案所示:有人可能会争辩说,提议的方法只会使事情复杂化,而且会隐藏&#34;事实上这些实际上是setter / getter方法。我不会说到目前已经存在setter / getter方法已经存在问题了。但是你现在的方法是在访问者中设置setter-setter和getter-setter。这会以一种难以包围的方式扩展访问者的状态空间

总结:

尽管有上述论点,我不会称之为“不良做法”。 - 也因为它根本不是常见的练习,而是一种非常具体的解决方案。这样做可能有理由和争论,但只要您不提供更多详细信息,就很难说您的特定情况是否属实,或者是否有更优雅的解决方案。

更新

有关详细信息:您说过

  当用户更改这些变量时,可能会出现

不一致

一个班级通常负责管理自己的状态空间,以确保它始终保持一致&#34;。而且,从某种意义上说,这是首先拥有类和封装的主要目的。吸气鬼+二传手有时被认为是邪恶的原因之一#34;不仅是可变性(通常应该最小化)。但也因为人们倾向于用getter + setter暴露类的属性,而不考虑适当的抽象。

具体如此:如果您有两个彼此依赖的变量xy,那么该类应该只是有方法

public void setX(int x) { ... }
public void setY(int y) { ... }

相反,应该(最好,大致)是一种方法,如

public void setState(int x, int y) { 
    if (inconsistent(x,y)) throw new IllegalArgumentException("...");
    ...
}

确保状态始终保持一致。

我不认为有一种干净地模仿C ++ friend函数的方法。您建议Consumer / Supplier方法可能作为解决方法是合理的。通过稍微不同的方法可以避免它可能导致的一些(并非所有)问题:

org.example包含您的主要类

class A {
    private int v;
    private int w;

    public void accept(SomeVisitor visitor) {
        // See below...
    }        
}

org.example也包含一个接口。此接口使用getter + setter方法公开A的内部状态:

public interface InnerA {
    void setV(int v);
    int getV();
    void setW(int w);
    int getW();
}

但请注意,主要类实现此接口!

现在,访问者可以驻留在不同的包装中,例如org.example.visitors。访问者可以使用专用方法访问InnerA对象:

public class SomeVisitor extends ParentVisitor {

    @Override
    public void visit(A a) {
        ...
    }

    @Override
    public void visit(InnerA a) {
        // Code that will, possibly, read and modify A.x
        ...
    }

acceptA方法的实施可以执行以下操作:

    public void accept(SomeVisitor visitor) {

        visitor.accept(this);

        visitor.accept(new InnerA() {
            @Override 
            public void setX(int theX) {
                x = theX;
            }
            @Override 
            public int getX() {
                return x;
            }
            // Same for y....
        });
    }        

因此,该类将专门将新创建的InnerA实例传递给访问者。此InnerA仅在访问时存在,并且仅用于修改创建它的特定实例。

中间解决方案可能是不定义此接口,而是引入类似

的方法
@Override
public void visit(Consumer<Integer> setter, Supplier<Integer> getter) {
    ...
}

@Override
public void visit(A a, Consumer<Integer> setter, Supplier<Integer> getter) {
    ...
}

根据真实的应用案例,必须进一步分析。

但同样:这些方法都没有规避一般性问题,即当您提供对包外某人的访问权限时,您将提供对所有人之外的访问权限。你的包....

附录1:一个A的类,但使用公共getter / setter方法。再见,封装:

class AccessibleA extends A {
    private Consumer<Integer> setter;
    ...
    AccessibleA() {
        EvilVisitor e = new EvilVisitor();
        e.accept(this);
    } 
    void setSetter(Consumer<Integer> setter) { this.setter = setter; }
    ...
    // Here's our public setter now:
    void setValue(int i) { setter.accept(i); }
}

class EvilVisitor {
    private AccessibleA accessibleA;
    ...
    public void setSetter(Consumer<Integer> setter) {
        accessibleA.setSetter(setter);
    }
    ...
}

附录2:

想象一下,你有一个像这样的课程

class Manipulator {
    private A a;
    Manipulator(A a) {
        this.a = a;
    }
    void manipulate() {
        int value = a.getValue();
        a.setValue(value + 42);
    }
}

现在想象一下,您希望将此类的编译时依赖性移除到类A。然后,您可以将其更改为不接受构造函数中的A实例,而是接受Supplier / Consumer对。但对于访客来说,这没有意义。

答案 1 :(得分:-1)

作为吸气者和制定者are evil anyway,你最好不要让事情比普通的吸气者和制定者更复杂。