English 中文(简体)
在实体生命的某个阶段更改实体的类型
原标题:Changing the type of an entity at some point of its life

I have the following hierarchy: Party is a base class, extended by Person and Corporation. I need to change the object class of an entity at some point of its life, and I don t know what s the best way to do this.

我正在模拟金融世界,所以我认为一方可以拥有一家公司的股份,而只有公司才能拥有股东。类似于以下内容:

class Party {
    public Set getShares();
}

class Corporation extends Party {
    public Set getShareholders();
    public void setShareholders(Party ss);

}

class Person extends Party {
    ... (some method still unknown)
}

I build the objects reading data from a source, and it can happen that at first I know only the name of a party, but I don t know whether it is a Person or a Corporation. But I need to create an object, so I create a generic Party. After that, it may happen that I come to know more infos, such that my Party was a Corporation. So, I need to change the class that represents that entity from Party to Corporation. Until now, my solution was to build a new object, copying the old data into it. But I m not satisfied with it, and I m wondering what s the best way, pattern or whatever else, to implement what I d like to achieve.

我想到了状态模式,但我认为它最适合其他情况。

一般来说,我不希望真的更改对象的类。我想要的是有一些机制使客户端类遵守不同类型的契约。也就是说,我不希望一个类能够在Person上调用setShareholders,我如何实现这一点并不重要,我的意思是,也许一个实体是Person这一事实可以用其他方式表示,而不是使用类Person。

最佳回答

一般来说,我并不想真正更改对象的类。我想要的是有一些机制使客户端类遵守不同类型的契约。也就是说,我不希望一个类能够在Person上调用setShareholders,我如何实现这一点并不重要,我的意思是,也许一个实体是Person这一事实可以用其他方式表示,而不是使用类Person。

最后一段让我想到了动态代理可以满足您的需求。如果您有一个”实体“E,则“是一个Person[可以]以其他方式表示,而不是使用类Person”。代理可以包装您的实体E并“实现”/仅呈现所需的接口Person(同时隐藏任何其他已实现的接口或有关E的实现详细信息)。

编辑:由于OP发现答案很有用,我添加了一个实用程序类和演示代码:

某些代码:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Utilities to support using dynamic proxies
 */
public class DynamicProxy {

    /**
     * An invocation handler that passes calls to its delegate. This class
     * could be subclassed to provide dynamic method invocation handling
     * while still being able to fall back to the delegate object s methods.
     *
     * @see InvocationHandler
     */
    public static class DelegatingInvocationHandler
    implements InvocationHandler {

        /** The object this proxy is wrapping */
        private final Object delegate;

        /**
         * Creates a delegate invocation handler around an object
         *
         * @param object
         *            The object to wrap
         */
        public DelegatingInvocationHandler(final Object delegate) {
            this.delegate = delegate;
        }

        /* (non-Javadoc)
         *
         * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object,
         * java.lang.reflect.Method, java.lang.Object[])
         */
        @Override
        public Object invoke(final Object proxy, final Method m,
                final Object[] args) throws Throwable {
            Object result;

            try {
                result = m.invoke(delegate, args);
            } catch (final InvocationTargetException e) {
                throw e.getTargetException();
            } catch (final Exception e) {
                throw new RuntimeException("unexpected invocation exception: "
                        + e.getMessage());
            }

            return result;
        }
    }

    /**
     * Create a dynamic proxy that implements only a specified subset of the
     * original object s implemented interfaces. The proxy allows you to
     * essentially hide the other interfaces implemented by the original
     * object.
     *
     * @param delegate
     *            the object that the proxy "proxies" for (a.k.a. the delegate
     *            that handles the calls the proxy "allows" through)
     * @param requiredInterface
     *            one of the interfaces of the delegate that the proxy exposes
     * @param moreInterfaces
     *            any additional interfaces of the delegate to expose
     * @return the proxy
     *             a proxy for the delegate that can be cast to any of the
     *             specified interfaces
     */
    public static <T> T createSelectiveProxy(final T delegate,
            final Class<T> requiredInterface,
            final Class<?>... moreInterfaces) {
        if (delegate == null) {
            throw new IllegalArgumentException(
                    "The delegate object cannot be null");
        }

        return createProxy(new DelegatingInvocationHandler(delegate),
                requiredInterface, moreInterfaces);
    }

    /**
     * Creates a proxy using the specified invocation handler.
     *
     * @param object
     *            the implementing object that proxy wraps
     * @param invocationHandler
     *            the interfaces
     * @param moreInterfaces
     *            the interfaces
     * @return the object
     */
    @SuppressWarnings("unchecked")
    public static <T> T createProxy(final InvocationHandler invocationHandler,
            final Class<T> requiredInterface,
            final Class<?>... moreInterfaces) {
        if (invocationHandler == null) {
            throw new IllegalArgumentException(
                    "The invocation handler cannot be null");
        }

        final int size = (moreInterfaces != null ? moreInterfaces.length : 0);
        final Class<?>[] interfaces = new Class<?>[size + 1];
        interfaces[0] = requiredInterface;
        System.arraycopy(moreInterfaces, 0, interfaces, 1, moreInterfaces.length);

        return (T) Proxy.newProxyInstance(invocationHandler.getClass()
                .getClassLoader(), interfaces, invocationHandler);
    }
}

演示:

public class DynamicProxyDemo {

    private interface A {
        void methodA();
    }

    private interface B {
        void methodB();
    }

    private static class Foo implements A, B {

        public void methodA() {
            System.out.println("A");
        }

        public void methodB() {
            System.out.println("B");
        }
    }

    private DynamicProxyDemo() {}

    public static void main(final String[] args) {
        final Foo foo = new Foo(); // implements both interfaces

        // calls foo s methods, but only A methods
        final A a = DynamicProxy.createSelectiveProxy(foo, A.class);

        // calls foo s methods, but only B methods
        final B b = DynamicProxy.createSelectiveProxy(foo, B.class);

        // calls foo s methods, but A and B methods
        final A ab = DynamicProxy.createSelectiveProxy(foo, A.class, B.class);

        System.out.println("
 *** Call a method A.methodA() on proxy  a ");
        a.methodA();

        try {
            System.out.println("
 *** Call a method B.methodB() on proxy  a  (throws exception)");
            ((B) a).methodB();
        } catch (final Exception ex) {
            ex.printStackTrace(System.out);
        }

        System.out.println("
 *** Call a method B.methodB() on proxy  b ");
        b.methodB();

        try {
            System.out.println("
 *** Call a method A.methodA() on proxy  b  (throws exception)");
            ((A) b).methodA();
        } catch (final Exception ex) {
            ex.printStackTrace(System.out);
        }

        System.out.println("
 *** Call a method A.methodA() on proxy  ab ");
        ab.methodA();

        System.out.println("
 *** Call a method B.methodB() on proxy  ab ");
        ((B) ab).methodB();

        // ClassCastException: $Proxy0 cannot be cast to DynamicProxy$Foo
        try {
            System.out.println("
 *** Call a method  A  of class  Foo  on proxy  a  (throws exception)");
            ((Foo) a).methodA();
        } catch (final Exception ex) {
            ex.printStackTrace(System.out);
        }

        // ClassCastException: $Proxy1 cannot be cast to DynamicProxy$Foo
        try {
            System.out.println("
 *** Call a method  B  of class  Foo  on proxy  b  (throws exception)");
            ((Foo) b).methodB();
        } catch (final Exception ex) {
            ex.printStackTrace(System.out);
        }

        // ClassCastException: $Proxy0 cannot be cast to DynamicProxy$B
        try {
            System.out.println("
 *** Call a method B.methodB() on proxy  a  (throws exception)");
            ((B) a).methodB();
        } catch (final Exception ex) {
            ex.printStackTrace(System.out);
        }

        // ClassCastException: $DynamicProxy1 cannot be cast to DynamicProxy$A
        try {
            System.out.println("
 *** Call a method A.methodA() on proxy  b  (throws exception)");
            ((A) b).methodA();
        } catch (final Exception ex) {
            ex.printStackTrace(System.out);
        }
    }
}

运行时间:

 *** Call a method A.methodA() on proxy  a 
A

 *** Call a method B.methodB() on proxy  a  (throws exception)
java.lang.ClassCastException: net.bertfernandez.reflection.$Proxy0 cannot be cast to net.bertfernandez.reflection.DynamicProxyDemo$B
    at net.bertfernandez.reflection.DynamicProxyDemo.main(DynamicProxyDemo.java:49)

 *** Call a method B.methodB() on proxy  b 
B

 *** Call a method A.methodA() on proxy  b  (throws exception)
java.lang.ClassCastException: net.bertfernandez.reflection.$Proxy1 cannot be cast to net.bertfernandez.reflection.DynamicProxyDemo$A
    at net.bertfernandez.reflection.DynamicProxyDemo.main(DynamicProxyDemo.java:59)

 *** Call a method A.methodA() on proxy  ab 
A

 *** Call a method B.methodB() on proxy  ab 
B

 *** Call a method  A  of class  Foo  on proxy  a  (throws exception)
java.lang.ClassCastException: net.bertfernandez.reflection.$Proxy0 cannot be cast to net.bertfernandez.reflection.DynamicProxyDemo$Foo
    at net.bertfernandez.reflection.DynamicProxyDemo.main(DynamicProxyDemo.java:73)

 *** Call a method  B  of class  Foo  on proxy  b  (throws exception)
java.lang.ClassCastException: net.bertfernandez.reflection.$Proxy1 cannot be cast to net.bertfernandez.reflection.DynamicProxyDemo$Foo
    at net.bertfernandez.reflection.DynamicProxyDemo.main(DynamicProxyDemo.java:81)

 *** Call a method B.methodB() on proxy  a  (throws exception)
java.lang.ClassCastException: net.bertfernandez.reflection.$Proxy0 cannot be cast to net.bertfernandez.reflection.DynamicProxyDemo$B
    at net.bertfernandez.reflection.DynamicProxyDemo.main(DynamicProxyDemo.java:89)

 *** Call a method A.methodA() on proxy  b  (throws exception)
java.lang.ClassCastException: net.bertfernandez.reflection.$Proxy1 cannot be cast to net.bertfernandez.reflection.DynamicProxyDemo$A
    at net.bertfernandez.reflection.DynamicProxyDemo.main(DynamicProxyDemo.java:97)
问题回答

我有一个解决方案。这并不漂亮,但也不是你的问题:

定义一个新类,翻译器。为翻译人员提供将个人转换为公司的操作,反之亦然。当你需要在两者之间切换时,把讨厌的逻辑放在翻译器中。至少所有这些逻辑都将被隔离并可以进行测试。

你的问题之所以如此困难,是因为你需要了解你的建筑是什么才能构建它。尤其是Builder模式,正是因为这个原因而失败。

另一种选择可能是使用黑板建筑模式。当您了解到有关您的政党拥有哪些财产的更多信息时,黑板的观察员可以尝试确定它是个人还是团体。

或者,你可以完全重新评估你的设计,并尝试从一个新的角度出发。

I believe, there is no other way. After you typed new you can no longer go down the class hierarchy. You can only go up.
Upcasting is very logical for, i think, obvious reasons. However, upcasting of object in Java (and in every language i know) is done only by upcasting references, not underlying object. Upcasting of underlying objects is impossible, as far as i know.
As for downcasting of underlying object - it is impossible too. It may be related to general design of language and, which is more important, memory allocation issues. I ll explain.
Suppose class Base can have maximum 4 Mb of RAM, and class Derived - 7 Mb.
So your memory looks like this(1 symbol = 1 Mb): BASEBASEOTHERDERIVED. And suppose you re telling system to convert second base class to Derived. System can do this: BASEDERIVEDERDERIVED. As you can see, object OTHER is corrupted this way. System can also do this: BASEDERIVEDOTHERDERIVED. But in this case it must move addresses of OTHER and DERIVED, which is expensive, maybe even dangerous and hard, especially if there are more than two objects in RAM. So the preffered solution is: BASE____OTHERDERIVEDDERIVED, where _____ is free space, which is similar to freeing memory and allocating new object.

创建<code>java.lang.Object</code>后,不能更改它的类。用基于原型的编程,如在JavaScript中实现的。然而,您正试图在Java中解决这个问题,这需要一些额外的工作。

我只会保留党的阶级,并使其更加聪明,以便能够处理其潜在类别之间的差异:个人和公司。以下代码片段决不是一个完整的解决方案,只是为了说明该方法:

class Party {
    PartyKind kind;
    Map<String, Property<?>> properties;

    public Property getProperty(String name) {
        return properties.get(name);
    }
}

class Property<T> {
    T value;
}

enum PartyKind {
    PERSON,
    CORPORATION
}

编辑:引入属性<;T>类使其更具类型安全性,并消除了难看的强制转换。

Decorator模式是一种实现目标的简单技术,尽管最多需要一个装饰器来包装最初的Party实例。

我对Java的类不是很熟悉,但维基百科的链接说这是可行的。

一般来说,我并不想真正更改对象的类。

我认为如果不改变对象的类,你就无法做到这一点。无论如何,我只会给你一个替代方案。这不是你问题的答案,但可能会给你一些想法。

抽象党课:

abstract class Party {
    protected String nameOfParty;
    private Set<String> shares=new HashSet<String>();
    public Party(String name) {
        nameOfParty=name;
    }
    public Set<String> getShares(){
        return shares;
    }
    public void addShare(Party corp) {
         shares.add(((Corp)corp).nameOfParty);//only add corporation s share.
    }
}

公司类别:

public class Corp extends Party {
    private Set<String> shareHolders = new HashSet<String>();

    public Corp(String name) {
        super(name);
    }

    public Corp(Party per) {
        super(per.nameOfParty);
    }

    public Set<String> getShareholders() {
        return shareHolders;
    }

    public void addShareholder(Party shareHolder) {
        this.shareHolders.add(shareHolder.nameOfParty);
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Name Of Corp : ").append(nameOfParty);
        sb.append("
Share s: ").append(this.getShares());
        sb.append("
Share Holder s : ").append(shareHolders);
        return sb.toString();
    }

}

人员类别:

public class Person extends Party {

    public Person(String name) {
        super(name);
    }

    public Person(Party name) {
        super(name.nameOfParty);
    }

    public void unknowMethod() {

    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Name Of Person : ").append(nameOfParty);
        sb.append("
Share s: ").append(this.getShares());
        return sb.toString();
    }

}

测试:

public class Test {
    public static void main(String[] args) {
    /*
     * Initially consider all party s to be person s 
     * or create party with anonymous class if party information
     * is not available at that time of creation.
     */
        Party google = new Party("Google") {}; //Party with anonymous class
        Party yahoo = new Person("Yahoo");
        Party george = new Person("George");
        Party ali = new Person("Ali");
        Set<Party> list = new HashSet<Party>();

        list.add(google);
        list.add(yahoo);
        list.add(george);
        list.add(ali);
        // later came to know that google and yahoo are corporation s
        google = changeToCorp(list, google);
        yahoo = changeToCorp(list, yahoo);

        for (Party pty : list) {
            if (pty instanceof Person) {
                Person p = (Person) pty;
                p.unknowMethod();// access method s of person
                p.addShare(yahoo);
                p.addShare(google);
                /*p.addShare(ali); 
                  will throw exception since ali is a person and cannot be added as share*/
                System.out.println("
" + pty);
            }
            if (pty instanceof Corp) {
                Corp c = (Corp) pty;
                c.addShareholder(george);// access method s of corp
                c.addShareholder(yahoo);
                c.addShare(google);
                c.addShareholder(ali);// share holder s can be either a person or a corporation
                System.out.println("
" + pty);
            }
        }

    }

    static Corp changeToCorp(Set<Party> se, Party per) {
        se.remove(per);
        Corp p = new Corp(per);
        se.add(p);
        return p;
    }

}




相关问题
Spring Properties File

Hi have this j2ee web application developed using spring framework. I have a problem with rendering mnessages in nihongo characters from the properties file. I tried converting the file to ascii using ...

Logging a global ID in multiple components

I have a system which contains multiple applications connected together using JMS and Spring Integration. Messages get sent along a chain of applications. [App A] -> [App B] -> [App C] We set a ...

Java Library Size

If I m given two Java Libraries in Jar format, 1 having no bells and whistles, and the other having lots of them that will mostly go unused.... my question is: How will the larger, mostly unused ...

How to get the Array Class for a given Class in Java?

I have a Class variable that holds a certain type and I need to get a variable that holds the corresponding array class. The best I could come up with is this: Class arrayOfFooClass = java.lang....

SQLite , Derby vs file system

I m working on a Java desktop application that reads and writes from/to different files. I think a better solution would be to replace the file system by a SQLite database. How hard is it to migrate ...

热门标签