Могу ли я заменить это наследство композицией? - PullRequest
0 голосов
/ 13 января 2012

Итак, давайте начнем с некоторого фона [модифицированного, чтобы сделать более конкретным [. Я понял, что могу заменить следующее:

abstract class MessageHandler {
  public void handleMessage(Message m) {
    validateMessage(m);
    processMessage(m);
  }

  protected void validateMessage(Message m) {
    // Default validation logic
  }

  protected abstract void processMessage(Message m);
}

class FakeMessageHandler extends MessageHandler {
  proteced void processMessage(Message m) {}
}

со следующим блоком кода:

interface IMessageProcessor {
  public void processMessage(Message m);
}

class FakeMessageProcessor implements IMessageProcessor {
  public void processMessage(Message m) {}
}

class MessageHandler {
  private IMessageProcessor processor;
  public MessageHandler(IMessageProcessor processor) {
    this.processor = processor;
  }

  public void handleMessage(Message message) {
    validateMessage(message);
    processor.processMessage(message);
  }

  protected void validateMessage(Message message) {
    // Default validation logic.
  }
}

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

class FakeMessageHandler extends MessageHandler {
  protected void validateMessage(Message m) {}
  protected void processMessage(Message m) {}
}

Внедренный интерфейс теперь нельзя использовать, поскольку в MessageHandler есть только 1 абстрактный метод. Тем не менее, я не могу заставить внедренный интерфейс содержать метод validateMessage(Message message), поскольку исходная точка использования абстрактного класса состояла в том, чтобы определить реализацию этого метода по умолчанию.

Существует ли какой-то элегантный шаблон для преобразования его в композицию для целей внедрения зависимостей и более простого тестирования?

1 Ответ

5 голосов
/ 15 января 2012

Вот мой взгляд на это:

Вместо расширения MessageHandler у меня есть один класс MessageHandler, который представляет собой композицию IMessageProcessor и IMessageValidator: Class diagram

Надеюсь, я правильно понял UML-диаграмму, это было давно ...

В любом случае, давайте посмотрим на MessageHandler:


class MessageHandler
{
    private IMessageProcessor processor;
    private IMessageValidator validator;

    public MessageHandler(IMessageProcessor processor)
    {
        this.processor = processor;

        //Use the given processor as validator, if it implements the IMessageValidator-interface
        if(IMessageValidator.class.isAssignableFrom(processor.getClass()))
        {
            this.validator = (IMessageValidator)processor;
        }   
    }

    public void setMessageValidator(IMessageValidator validator)
    {
        this.validator = validator;
    }

    public void handleMessage(Message message)
    {
        validateMessage(message);
        processor.processMessage(message);
        System.out.println("Message " + message + " handled by MessageHandler");
    }

    protected void validateMessage(Message message)
    {
        if(validator != null)
        {
            validator.validateMessage(message);
        }
        else
        {
            System.out.println("No IMessageValidator-implementation set, using default validation for message " + message);
        }
    }
}

MessageHandler имеет два закрытых члена, IMessageProcessor и IMessageValidator (это состав из процессора и валидатора). Валидатор можно оставить неустановленным, в этом случае при обработке сообщения будет задействована логика проверки по умолчанию.

В этом примере, если переданный процессор также реализует интерфейс IMessageValidator, он будет использоваться в качестве валидатора. Это, вероятно, то, что вы хотели, потому что вы можете использовать один и тот же конструктор для построения MessageHandlers, используя логику валидации или пользовательской валидации по умолчанию, в зависимости от того, реализует ли переданный объект только IMessageProcessor или IMessageProcessor и IMessageValidator (для удобства я расширил третий интерфейс, IValidatingMessageProcessor из этих интерфейсов). Если логика валидатора реализована отдельно (реализует только IMessageValidator), ее можно установить с помощью метода setValidator.

Нет необходимости расширять MessageHandler, поскольку вы можете реализовать логику обработки и проверки вне обработчика, либо отдельно, либо в одном классе, который реализует как обработку, так и проверку.

Вот классы, которые я использовал, надеюсь, это поможет:

Zip-пакет в MediaFire

Text-форма:

Message.java:


public class Message 
{
    private int number;

    public Message(int number)
    {
        this.number = number;
    }

    public String toString()
    {
        return "Msg " + number;
    }
}

IMessageProcessor.java:


interface IMessageProcessor 
{
  public void processMessage(Message m);
}

IMessageValidator.java:


public interface IMessageValidator 
{
    public void validateMessage(Message m);
}

IValidatingMessageProcessor.java:


public interface IValidatingMessageProcessor extends IMessageProcessor, IMessageValidator
{
}

FakeMessageProcessor.java:


public class FakeMessageProcessor implements IMessageProcessor
{
    public void processMessage(Message m)
    {
        System.out.println("Using FakeMessageProcessor to process message " + m);
    }
}

FakeMessageValidator.java:


public class FakeMessageValidator implements IMessageValidator
{
    public void validateMessage(Message m)
    {
        System.out.println("Using FakeMessageValidator to validate message " + m);      
    }
}

FakeMessageProcessorAndValidator.java:


public class FakeMessageProcessorAndValidator implements IValidatingMessageProcessor
{
    public void validateMessage(Message m)
    {
        System.out.println("Using FakeMessageProcessorAndValidator for validating message " + m);
    }

    public void processMessage(Message m)
    {
        System.out.println("Using FakeMessageProcessorAndValidator for processing message " + m);       
    }
}

Простое тестирование main для вышеперечисленных классов (только вывод данных):


public class MessageTest
{   
    public static void main(String[] args)
    {
        //Using processor implementing only IMessageProcessor, MessageHandler will use default validation
        IMessageProcessor processor = new FakeMessageProcessor();
        MessageHandler handler = new MessageHandler(processor);

        handler.handleMessage(new Message(1));

        //Setting separate validator to existing MessageHandler-instance
        handler.setMessageValidator(new FakeMessageValidator());

        handler.handleMessage(new Message(2));

        //Using processor implementing both IMessageProcessor and IMessageValidator
        processor = new FakeMessageProcessorAndValidator();
        handler = new MessageHandler(processor);

        handler.handleMessage(new Message(3));
    }
}

выход:


No IMessageValidator-implementation set, using default validation for message Msg 1
Using FakeMessageProcessor to process message Msg 1
Message Msg 1 handled by MessageHandler
Using FakeMessageValidator to validate message Msg 2
Using FakeMessageProcessor to process message Msg 2
Message Msg 2 handled by MessageHandler
Using FakeMessageProcessorAndValidator for validating message Msg 3
Using FakeMessageProcessorAndValidator for processing message Msg 3
Message Msg 3 handled by MessageHandler
...