Как организовать ОО дизайн с двумя разными типами пользователей - PullRequest
3 голосов
/ 15 мая 2011

У меня два разных типа пользователей, и я сопоставил их с двумя классами Java UserWheel и UserSea , и у них есть общий абстрактный суперкласс, называемый User . Данные, сохраненные для этих типов пользователей, примерно одинаковы, но поведение отличается.

Затем я создал абстрактный класс с именем UserCollection с производными классами UserWheelCollection и UserSeaCollection для поиска подпользователей или загрузки подпользователя.

Затем я добавил абстрактный метод в класс UserCollection с подписью

public abstract List<User> listAllSubusers()

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

Затем я хочу добавить новый метод в UserCollection с подписью public User loadById (int idUser) . В этом случае реализация будет такой же, за исключением того факта, что возвращенный пользователь будет экземпляром UserWheel или UserSea . Я не хочу использовать абстрактный метод в базовом классе из-за дублирования кода.

Я могу проверить конкретный класс UserCollection с помощью instanceof и создать соответствующий подкласс, но он не кажется объектно-ориентированным и нарушает принцип открытия-закрытия.

Другая идея заключается в добавлении абстрактного метода createNewUser () к UserCollection и конкретных реализаций в подклассах для возврата нового экземпляра, поэтому базовый класс просто вызовет этот метод createNewUser ().

Как вы думаете, этот второй путь имеет смысл? Или вы бы организовали вещи по-другому и как?


UPDATE . Текущая ситуация:

abstract class User
   public String getAddress()
   public void setAddress()
   ...

class UserSea extends User
class UserWheel extends User

abstract class UserCollection
   protected abstract User createNewUser();
   public abstract List<User> listAllSubUsers();
   public User loadById(int idUser) {
       User newUser = createNewUser();
       //populate it
       return newUser;
   }

class UserSeaCollection
   protected User createNewUser() {
        return new UserSea();
   }
   public List<User> listAllSubusers()

class UserWheelCollection
   protected User createNewUser() {
       return new UserWheel();
   }
   public List<User> listAllSubusers()

Я попытался понять шаблон стратегии, предложенный trashgod, и вот моя первая попытка:

interface SubuserManagement
    List<User> listAllSubUsers();
    ...

interface UserCrud
   void create();
   User readById(int idUser);
   void update();
   void delete();

class UserSeaCollection implements SubUserManagement, UserCrud

   private SubUserManagement subuserBehavior = new SubUserManagementSeaImplementation();
       private UserCrud userCrudBehavior = new UserCrud();

   void create {
       subUserBehavior.create();
   }
   ...

class UserWheelCollection implements SubUserManagement, UserCrud
       ...

class SubUserManagementWheelImplementation implements SubUserManagement
    List<User> listAllSubUsers();

class SubUserManagementSeaImplementation implements SubUserManagement
    List<User> listAllSubUsers();

class UserCrudImplementation implements UserCrud //only 1 implementation
   void create();
   User readById(int idUser);
   void update();
   void delete();

В этой первой попытке я создал UserCollectionWheel и UserCollectionSea, которые больше не используют общий суперкласс, но реализуют те же интерфейсы. Фактическая реализация находится во внешних классах.

Теперь UserCollectionWheel и UserCollectionSea - это действительно один и тот же класс, с единственным отличием в поведении, которое я им назначаю. В качестве альтернативы я мог бы написать только один класс с сеттерами:

UserCollection userColl = new UserCollection();
userColl.setSubUserBehavior(new SubUserManagementSeaImplementation());
userColl.setCrudBehavior(new UserCrud());

Но инициализация была бы громоздкой, особенно если бы у меня было больше классов поведения. Так что я делаю не так? Как правильно это организовать?

ОБНОВЛЕНИЕ 2 : я написал сообщение в блоге с дизайном, который я реализовал.

Ответы [ 3 ]

3 голосов
/ 15 мая 2011

FWIW, вот мое мнение о шаблонном подходе Trashgod. Это не ответ, а вспомогательное объяснение ответа Трашгода.

public interface UserStore<T extends User> {
    public T create(int id);
    public List<T> listAll();
}

public class SeaUserStore implements UserStore<SeaUser> {
    public SeaUser create(int id) { return new SeaUser(id); }
    public List<SeaUser> listAll() { whatever }
}

// the dry counterpart of 'sea' is either 'land' or 'road', not 'wheel' :)
public class RoadUserStore implements UserStore<RoadUser> {
    public RoadUser create(int id) { return new RoadUser(id); }
    public List<RoadUser> listAll() { whatever }
}

public class UserCollection<T extends User> {
    private UserStore<T> store;
    public UserCollection(UserStore<T> store) {
        this.store = store;
    }
    public List<T> listAll() {
        return store.listAll();
    }
    public T getById(int id) {
        T user = store.create(id);
        // populate
        return user;
    }
}

Это оставляет на усмотрение клиента создание UserCollection. Вы могли бы обернуть это; сделайте конструктор UserCollection закрытым и добавьте:

public class UserCollection<T extends User> {
    public static UserCollection<SeaUser> createSeaUserCollection() {
        return new UserCollection<SeaUser>(new SeaUserStore());
    }
    public static UserCollection<RoadUser> createRoadUserCollection() {
        return new UserCollection<RoadUser>(new RoadUserStore());
    }
}
3 голосов
/ 15 мая 2011

Вместо наследования поведения рассмотрите возможность его инкапсуляции с использованием интерфейсов в шаблоне стратегии .Пользователи будут отличаться наличием двух конкретных реализаций interface ListSubUsersStrategy, interface CreateUserStrategy и т. Д.

См. Также соответствующий шаблон моста .

Приложение: В приведенном ниже примере каждый пользователь имеет конкретную стратегию поиска подпользователей.В частности, listAllSubUsers() вызывает метод интерфейса, автоматически отправляя правильную конкретную реализацию.Этот шаблон не освобождает вас от написания конкретных реализаций интерфейса, но он удаляет их из пары, гарантируя, что изменение одного не сломает другого.

Консоль:

A has wheel users.
B has sea users.
C has wheel users.

Код:

import java.util.ArrayList;
import java.util.List;

/** @see http://stackoverflow.com/questions/6006323 */
public class UserMain {

    private static final List<User> users = new ArrayList<User>();

    public static void main(String[] args) {
        users.add(new User("A", new WheelStrategy()));
        users.add(new User("B", new SeaStrategy()));
        users.add(new User("C", new WheelStrategy()));
        for (User user : users) {
            user.listAllSubUsers();
        }
    }

    private static class User {

        private String name;
        private SubUsersStrategy suStrategy;

        public User(String name, SubUsersStrategy suStrategy) {
            this.name = name;
            this.suStrategy = suStrategy;
        }

        public void listAllSubUsers() {
            System.out.print(name + " manages ");
            List<User> subUsers = suStrategy.getList();
        }
    }

    private interface SubUsersStrategy {

        List<User> getList();
    }

    private static class WheelStrategy implements SubUsersStrategy {

        @Override
        public List<User> getList() {
            System.out.println("wheel users.");
            return null;
        }
    }

    private static class SeaStrategy implements SubUsersStrategy {

        @Override
        public List<User> getList() {
            System.out.println("sea users.");
            return null;
        }
    }
}
1 голос
/ 15 мая 2011

Я мог бы проверить конкретный класс UserCollection с instanceof и создать соответствующий подкласс, но он не кажется объектно-ориентированным и нарушает принцип открытия-закрытия.

Это сломало бы Принцип подстановки Лискова , который действительно является правилом проверки наличия иерархии надежных объектов.Правило гласит, что если метод ожидает User в качестве аргумента, не должно иметь значения, является ли пользователь CompledUser, DirtyUser или любым другим пользователем.(Следовательно, у вас может не быть if проверяющего экземпляра и т. Д.).

Наследование - это все о отношениях "is-a", что в основном означает, что вы должны иметь возможность взять любой производный объект и передать егометод, который ожидает базовый класс.Если вы не можете этого сделать, у вас много проблем.Подобные проблемы связаны с тем, что вы потерпели неудачу с вашими классами.

Другая идея заключается в добавлении абстрактного метода createNewUser () к UserCollection и конкретных реализаций в подклассах для возврата нового экземпляра, поэтому базовыйкласс просто вызовет этот метод createNewUser ()

Это лучший подход, так как вызывающему, если createNewUser не важно, какого типа пользователя он получит.Все, что он знает, это то, что это пользователь.

Подход называется фабричным методом.

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