在派生类构造函数(或工厂)中初始化基类的模式

发布于 2024-11-25 16:33:28 字数 641 浏览 0 评论 0 原文

假设您有一个派生类,其中基类是您无法修改的。基类具有许多状态(许多非常量私有成员)和许多构造函数,具有不同数量的参数来初始化状态的某些子集(当然,子集的大小因构造函数而异)。

现在我的派生类是基类的一个非常轻量级的包装器。我们假设它没有添加自己的状态,并且仅稍微修改了几个方法的行为(可能围绕 super.originalMethod() 调用进行一些额外的日志记录)。

我遇到的问题是,我想获取基类的一个对象,并创建它的“副本”,具有相同的状态,但作为派生类的实例。

事实证明这很困难。我无法调用基类的“最完整”构造函数,通过调用 getter 从源传递所有状态,因为根据基类的构造方式,某些状态值可能会被此构造函数拒绝。例如,您可以创建一个带有 0-arg 构造函数的默认对象,任何多个值都将为 null。但是,在允许您指定这些值的构造函数中传递空值是不合法的。

此外,上面的方法很脆弱,因为如果对基类进行修改,会添加更多状态,并且添加“甚至更完整”的构造函数(或无法在构造函数中设置的状态,而只能通过访问器方法设置),副本将不再完整。

我想要的是像`clone(),而是初始化相同类型的新对象,初始化派生类的基类成员。我想这样的事情是不存在的。关于可能提供同等功能的模式有什么建议吗?

请记住,我无法修改基类。如果我可以的话,这会容易得多。

Imagine you have a derived class, where the base class is something you cannot modify. The base class has a lot of state (many non-constant private members) and many constructors, with varying numbers of arguments to initialize some subset of the state (the size of the subset varies by constructor, of course).

Now my derived class is a very lightweight wrapper over the base class. Let's assume it adds no state of its own, and only slightly modifies the behavior of a couple methods (perhaps doing some additional logging around a super.originalMethod() call).

The issue I have is that I want to take an object of the base class, and create a "copy" of it, with the same state, but as an instance of my derived class.

This is proving difficult. I can't call the "most complete" constructor of the base class, passing all the state from the source by calling getters, since depending on how the base class was constructed, some of the state values may be rejected by this constructor. For example, you can create a default object with a 0-arg ctor, any many values will be null. It is not, however, legal to pass null values in the ctor that allows you to specify these values.

Furthermore, the method above is fragile because if a modification to the base class occurs which adds more state, and "even more complete" constructor (or state which can't be set in a constructor, but only through accessor methods) is added, the copy won't be complete any more.

What I want is like `clone(), but rather initializing a new object of the same type, initialize the base class members of the derived class. I guess such a thing doesn't exist. Any suggestions on a pattern that might offer something equivalent?

Keep in mind I cannot modify the base class. If I could, this would be much easier.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

彡翼 2024-12-02 16:33:28

如果可以重写所有公共方法,则可以将源对象保存为委托

Class D extends B
    B src;
    D(B src){ super(whatever); this.src=src; }

    public method1(){ src.method1(); }

If you can override all public methods, you can save the source object as the delegate

Class D extends B
    B src;
    D(B src){ super(whatever); this.src=src; }

    public method1(){ src.method1(); }
残疾 2024-12-02 16:33:28

优先考虑组合而不是继承,也许可以通过创建包装类(如下所示)。如果您的基类使用接口,则您的包装类可以实现相同的接口并将调用委托给基类(装饰器)。

但是,没有像您所描述的继承那样的稳健策略。即使您使用反射来执行深层复制,正如您所指出的,实现也可能会发生变化。您已经破坏了封装,并且您的代码将与基类紧密耦合。

public static void main(final String[] args) {
    final Base base = new Base("Hello");
    base.printState(); // Prints "Hello"
    final Wrapper wrapper = new Wrapper(base);
    wrapper.printState(); // Prints "Wrapper says Hello"
    wrapper.clone().printState(); // Prints "Wrapper says Hello"
}

private static class Wrapper {

    private final Base base;

    public Wrapper(final Base base) {
        this.base = base;
    }

    public Wrapper clone() {
        return new Wrapper(base);
    }

    public void printState() {
        System.out.printf("Wrapper says ");
        base.printState();
    }
}

private static class Base {

    private Object state;

    public Base(final Object state) {
        if (state == null) {
            throw new IllegalArgumentException("State cannot be null");
        }
        this.state = state;
    }

    public void printState() {
        System.out.println(state);
    }
}

Favour composition over inheritance, perhaps by creating a wrapper class (as below). If your base class utilizes interfaces, your wrapper class can implement the same interfaces and delegate calls to the base class (decorator).

However, there is no robust strategy to do as you describe with inheritance. Even if you used reflection to perform a deep copy, as you pointed out, the implementation may change. You have broken encapsulation and your code would be intimately coupled with the base class.

public static void main(final String[] args) {
    final Base base = new Base("Hello");
    base.printState(); // Prints "Hello"
    final Wrapper wrapper = new Wrapper(base);
    wrapper.printState(); // Prints "Wrapper says Hello"
    wrapper.clone().printState(); // Prints "Wrapper says Hello"
}

private static class Wrapper {

    private final Base base;

    public Wrapper(final Base base) {
        this.base = base;
    }

    public Wrapper clone() {
        return new Wrapper(base);
    }

    public void printState() {
        System.out.printf("Wrapper says ");
        base.printState();
    }
}

private static class Base {

    private Object state;

    public Base(final Object state) {
        if (state == null) {
            throw new IllegalArgumentException("State cannot be null");
        }
        this.state = state;
    }

    public void printState() {
        System.out.println(state);
    }
}
不必你懂 2024-12-02 16:33:28

正如其他人所指出的,很自然地会想到通过使用委托并将其实现为代理或装饰器来解决这个问题。处理这些模式的标准方法要求您有一个接口,而不是像 Java 的动态代理那样在基础上有一个具体的类。

但是,您可以使用 cglibcglib 来通过具体类完成类似的事情。 jboss.org/javassist" rel="nofollow">javassist。

通过足够的运行时 JVM 修补,也许通过上述之一,或者使用 AspectJ,我认为你甚至可以使您现有的类实现新定义的接口。

Hibernate 为所有持久类创建代理,而不要求它们实现接口,我相信它使用 cglib 来做到这一点。

As others have noted, it's natural to think of solving this by using delegation and implementing it as a proxy or decorator. The standard ways of dealing with these patterns require that you have an interface rather than a concrete class at the base, as does Java's dynamic proxy.

However, you can accomplish similar things with concrete classes using cglib or javassist.

With sufficient runtime JVM tinkering, perhaps through one of the above, or with AspectJ, I think you can even make your existing class implement a newly defined interface.

Hibernate creates proxies for all persistent classes without requiring that they implement an interface, and I believe it uses cglib to do this.

舟遥客 2024-12-02 16:33:28

我注意到有些人建议您同时使用组合和继承(请参阅下面的反模式示例)。

请将此作为最后的手段。除了引入冗余状态之外,您的子对象还将公开完全被忽略的状态和行为。这将导致 API 极具误导性。

public static void main(final String[] args) {
    final Base base = new Base("Hello");
    base.printState(); // Prints "Hello"
    final Wrapper wrapper = new Wrapper(base);

    wrapper.changeState("Goodbye");

    wrapper.printState(); // Prints "Wrapper says Hello"
    wrapper.clone().printState(); // Prints "Wrapper says Hello".

    // It seems my state change was completely ignored. What a confusing API...
}

private static class Wrapper extends Base {

    private final Base base;

    public Wrapper(final Base base) {
        super("Make something up; this state isn't used anyway");
        this.base = base;
    }

    public Wrapper clone() {
        return new Wrapper(base);
    }

    public void printState() {
        System.out.printf("Wrapper says ");
        base.printState();
    }
}

private static class Base {

    private Object state;

    public Base(final Object state) {
        if (state == null) {
            throw new IllegalArgumentException("State cannot be null");
        }
        this.state = state;
    }

    public void changeState(final Object state) {
        this.state = state;
    }

    public void printState() {
        System.out.println(state);
    }
}

编辑:实际上,不要这样做。曾经。这是一个非常非常可怕的策略。如果您无法管理与基类状态的所有交互(这又使其成为一个非常脆弱的解决方案),那么将会发生非常糟糕的事情。例如,如果我按如下方式修改基类:

private static class Base {

    ...

    // A new method
    public Object getState() {
        return state;
    }

    ...
}

哦天哪......

final Wrapper wrapper = new Wrapper(new Base("Foo"));
System.out.println(wrapper.getState()); // Prints "Make something up; this state isn't used anyway"

I notice some people are recommending you use both composition and inheritance (see below for an example of this anti-pattern).

Please do this only as a last resort. In addition to introducing redundant state, your child object will expose state and behaviour that is completely ignored. This will lead to a very misleading API.

public static void main(final String[] args) {
    final Base base = new Base("Hello");
    base.printState(); // Prints "Hello"
    final Wrapper wrapper = new Wrapper(base);

    wrapper.changeState("Goodbye");

    wrapper.printState(); // Prints "Wrapper says Hello"
    wrapper.clone().printState(); // Prints "Wrapper says Hello".

    // It seems my state change was completely ignored. What a confusing API...
}

private static class Wrapper extends Base {

    private final Base base;

    public Wrapper(final Base base) {
        super("Make something up; this state isn't used anyway");
        this.base = base;
    }

    public Wrapper clone() {
        return new Wrapper(base);
    }

    public void printState() {
        System.out.printf("Wrapper says ");
        base.printState();
    }
}

private static class Base {

    private Object state;

    public Base(final Object state) {
        if (state == null) {
            throw new IllegalArgumentException("State cannot be null");
        }
        this.state = state;
    }

    public void changeState(final Object state) {
        this.state = state;
    }

    public void printState() {
        System.out.println(state);
    }
}

EDIT: Actually, just don't do this. Ever. It's a horrible, horrible strategy. If you fail to manage all interaction with base-class state (which again, makes it a very brittle solution) then very bad things will occur. For example, if I modify base class as follows:

private static class Base {

    ...

    // A new method
    public Object getState() {
        return state;
    }

    ...
}

Oh dear...

final Wrapper wrapper = new Wrapper(new Base("Foo"));
System.out.println(wrapper.getState()); // Prints "Make something up; this state isn't used anyway"
多情癖 2024-12-02 16:33:28

这看起来像是 Proxy 的工作。 (也许如果你用谷歌搜索,你可以找到更好的代理实现,但我认为标准的代理实现已经很不错了。)

Implemnet 和 InitationHandler 像这样

class Handler implements InvocationHandler
{
    private Thingie thingie ;

    public Handler ( Thingie thingie )
    {
        this . thingie = thingie ;
    }

    public Object invoke ( Object proxy , Method method , Object [ ] args ) thro
ws Throwable
    {
        if ( method . getName ( ) . equals ( "target" ) )
            {
                LOG . log ( this ) ;
            }
        return method . invoke ( this . thingie , args ) ;
    }
}

This looks like a job for Proxy. (Probably if you google, you can find a better proxy implementation, but the standard one is in my opinion good enuf.)

Implemnet an InvocationHandler like this

class Handler implements InvocationHandler
{
    private Thingie thingie ;

    public Handler ( Thingie thingie )
    {
        this . thingie = thingie ;
    }

    public Object invoke ( Object proxy , Method method , Object [ ] args ) thro
ws Throwable
    {
        if ( method . getName ( ) . equals ( "target" ) )
            {
                LOG . log ( this ) ;
            }
        return method . invoke ( this . thingie , args ) ;
    }
}
夏有森光若流苏 2024-12-02 16:33:28

如果基类不提供对克隆的内置支持,则没有任何真正好的方法。恕我直言,如果正确的模式是将类分为三类:

-1-从根本上讲,在不破坏类不变量的情况下,无论如何都无法有意义地克隆的类。

-2- 可以在不破坏类不变量的情况下克隆的类,但可用于派生无法有意义地克隆的其他类。

-3- 可以克隆的类,并且仅用于派生同样可以克隆的类。

-2- 或 -3- 类型的类应提供受保护的虚拟克隆方法,该方法将调用父级的实现(如果它们是一个),或 Object.Clone (如果没有父级实现),然后执行任何类-具体清理。 -3- 类型的类应该提供一个公共克隆方法,该方法将调用虚拟方法并将结果类型转换为正确的类型。从类型 -2- 派生的类型 -1- 的可继承类应该使用函数以外的其他内容来隐藏受保护的克隆方法。

如果无法将受保护的克隆方法添加到父类中,则无法构造可克隆的派生类,该派生类在父类实现细节方面不会变得脆弱。但是,如果根据上述模式构造父类,则可以在派生类中干净地实现克隆。

If the base class doesn't provide built-in support for cloning, there isn't any really good way. IMHO, if the right pattern is to divide classes into three categories:

-1- Classes which fundamentally cannot be meaningfully be cloned, by any means, without breaking class invariants.

-2- Classes which can be cloned without breaking class invariants, but which may be used to derive other classes which cannot meaningfully be cloned.

-3- Classes which can be cloned, and which will only be used to derive classes which can likewise be cloned.

Classes of type -2- or -3- should provide a protected virtual cloning method which will call the parent's implementation (if their is one), or Object.Clone (if there isn't a parent implementation) and then do any class-specific cleanup. Classes of type -3- should provide a public cloning method which will call the virtual method and typecast the result to the proper type. Inheritable classes of type -1- which derive from those of type -2- should shadow the protected cloning method with something other than a function.

If there's no way to add a protected cloning method into the parent class, there's no way to construct a cloneable derived class which won't be brittle with regard to parent-class implementation details. If a parent class is constructed according to the above pattern, though, cloning will be implementable cleanly in derived classes.

甜点 2024-12-02 16:33:28

由于继承的工作方式,我认为您根本无法以这种方式分配它。假设您的基类是“A”类型。您创建类型“B”的包装类。您可以将“B”的实例分配给类型“A”,但不能将“A”的实例分配给类型“B”。

I don't think you're going to be able to assign it that way at all, due to the way that inheritence works. Let's say that your base class is of type "A". You create your wrapper class of type "B". You can assign an instance of "B" to type "A", but you cannot assign an instance of "A" to type "B".

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文