Изменение типа объекта в какой-то момент его жизни - PullRequest
3 голосов
/ 11 октября 2010

У меня есть следующая иерархия: Party - это базовый класс, расширенный Person и Corporation. Мне нужно изменить объектный класс объекта в какой-то момент его жизни, и я не знаю, как лучше это сделать.

Я моделирую финансовый мир, так что у меня есть партия, которая может владеть акциями Корпорации, в то время как только Корпорация может иметь акционеров. Примерно так:

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)
}

Я создаю объекты, считывающие данные из источника, и может случиться так, что сначала я знаю только название стороны, но не знаю, будет ли это Человек или Корпорация. Но мне нужно создать объект, поэтому я создаю общую партию. После этого может случиться так, что я узнаю больше информации, такой, что моя партия была корпорацией. Итак, мне нужно изменить класс, который представляет эту сущность, с партии на корпорацию. До сих пор моим решением было создание нового объекта, копирование в него старых данных. Но меня это не устраивает, и я задаюсь вопросом, каков наилучший способ, шаблон или что-то еще для реализации того, чего я хотел бы достичь.

Я подумал о шаблоне состояния , но думаю, что он лучше всего подходит для других ситуаций.

В общем, я не хочу, чтобы действительно менял класс объектов. Я хочу иметь некоторый механизм, чтобы заставить клиентские классы подчиняться контрактам разных типов. Т.е. я не хочу, чтобы класс мог вызывать setShareholder на Person, и не важно, как я могу этого достичь, я имею в виду, что, возможно, тот факт, что сущность является Person, может быть представлен другими способами, чем использование класс персоны.

Ответы [ 6 ]

2 голосов
/ 12 октября 2010

В общем, я не хочу действительно менять класс объектов. Я хочу иметь некоторый механизм, чтобы заставить клиентские классы подчиняться контрактам разных типов. Т.е. я не хочу, чтобы класс мог вызывать setShareholder на Person, и не важно, как я могу этого достичь, я имею в виду, что, возможно, тот факт, что сущность является Person, может быть представлен другими способами, чем использование класс персоны.

Этот последний абзац заставил меня подумать, что динамический прокси может удовлетворить ваши потребности. Если у вас есть « сущность » E, то « - это Person [который] может быть представлен другими способами, чем использование класса Person ». Прокси может обернуть вашу сущность E и «внедрить» / представить только желаемый интерфейс Person (скрывая при этом любые другие реализованные интерфейсы или подробности реализации о E).

Редактировать: Поскольку ОП нашел ответ полезным, я добавляю служебный класс и демонстрационный код:

Код:

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("\n *** Call a method A.methodA() on proxy 'a'");
        a.methodA();

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

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

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

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

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

        // ClassCastException: $Proxy0 cannot be cast to DynamicProxy$Foo
        try {
            System.out.println("\n *** 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("\n *** 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("\n *** 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("\n *** 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)
0 голосов
/ 13 октября 2010

В общем, я не хочу действительно менять класс объектов.

Я не думаю, что без изменения класса объекта вы сможете это сделать. В любом случае, я просто покажу вам альтернативу. Это не ответ на ваш вопрос, аможет дать вам некоторые идеи.

Класс абстрактной вечеринки:

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("\nShare's: ").append(this.getShares());
        sb.append("\nShare 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("\nShare'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("\n" + 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("\n" + pty);
            }
        }

    }

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

}
0 голосов
/ 13 октября 2010

Шаблон Decorator - это простой метод для достижения вашей цели, хотя и не более одного декоратора, обертывающего первоначальный экземпляр Party.эта ссылка в Википедии говорит, что это выполнимо.

0 голосов
/ 11 октября 2010

Вы не можете изменить класс java.lang.Object после того, как он был создан. Проблему, которую вы пытаетесь решить, вероятно, было бы тривиально решить с помощью программирования на основе прототипов , как это реализовано, например, в. 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
}

РЕДАКТИРОВАТЬ: введение класса Property<T> делает его более безопасным для типов и избавляет от уродливых приведений.

0 голосов
/ 11 октября 2010

У меня есть решение.Это не красиво, но и ваша проблема:

Определите новый класс, Переводчик.Дайте переводчику операции, которые превращают человека в корпорацию, и наоборот.Когда вам нужно поменяться местами, поместите противную логику в переводчик.По крайней мере, вся эта логика будет изолирована и может быть проверена.

Причина, по которой ваша проблема настолько сложна, заключается в том, что вам нужно знать , что ваше здание, чтобы построить его.В частности, шаблон Builder не работает по этой причине.

Другой вариант может заключаться в использовании Blackboard Architectural pattern .Когда вы узнаете больше о том, какими свойствами обладает ваша партия, наблюдатели на доске могут попытаться определить, является ли она человеком или корпорацией.

В качестве альтернативы, вы можете полностью пересмотреть свой дизайн и попытаться прийти под новым углом.

0 голосов
/ 11 октября 2010

Полагаю, другого пути нет. После того, как вы ввели «new», вы больше не можете идти вниз по иерархии классов. Вы можете только подняться.
Апкастинг очень логичен, я думаю, по понятным причинам. Тем не менее, обновление объекта в Java (и на любом языке, который я знаю) осуществляется только путем передачи ссылок, а не базового объекта. Насколько я знаю, апгрейд основных объектов невозможен.
Что касается понижения базового объекта - это тоже невозможно. Это может быть связано с общим дизайном языка и, что более важно, с проблемами выделения памяти. Я объясню.
Предположим, класс Base может иметь максимум 4 МБ ОЗУ, а класс Derived - 7 МБ.
Итак, ваша память выглядит так (1 символ = 1 Мб): BASEBASEOTHERDERIVED. И предположим, вы говорите системе преобразовать второй базовый класс в Derived. Система может сделать это: BASEDERIVEDERDERIVED. Как видите, объект «ДРУГОЙ» поврежден таким образом. Система также может сделать это: BASEDERIVEDOTHERDERIVED. Но в этом случае он должен перемещать адреса OTHER и DERIVED, что дорого, может быть, даже опасно и сложно, особенно если в оперативной памяти более двух объектов. Таким образом, предпочтительное решение: BASE____OTHERDERIVEDDERIVED, где _____ - свободное пространство, которое аналогично освобождению памяти и выделению нового объекта.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...