Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Платформа Java - шаблоны, технологии, хитрости


выпуск 2

...

Проверка корректности значений обязательная процедура. Для сохранения инварианта класса приходиться включать код по проверке в большинство set методов. Это смешивает основную логику приложения с бесконечными if-else конструкциями. Однако есть простой способ сделать такие проверки единообразными и легко читаемыми.

Проверка корректности значений и паттерн Компоновщик

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

void setAge (int age)
{
 if (age < 18)
          System.out.println (“Значение слишком мало”);
 else if (age > 100)
  System.out.println (“Значение слишком велико”);
 else
           .....
}

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

Для начала, напишем интерфейс:
interface Constraint <T>
{
      //корректно ли значение
      public boolean isValid (T t);

      //сообщение об ошибке в случае провала
      public String getMessage ();
}

Этот интерфейс будут реализовывать все классы, проверяющие определенные условия.

Для примера напишем несколько таких классов. Следующие 2 класса накладывают условия на максимальное и минимальное значение целого.

class MinValue implements Constraint <Integer>
{
      private int min;

      public MinValue (int min)
      {
            this.min = min;
      }

      public boolean isValid (Integer t)
      {

            return t >= min;
      }

      public String getMessage ()
      {
            return "число слишком мало";
      }
}

class MaxValue implements Constraint <Integer>
{
      private int max;

      public MaxValue (int max)
      {
            this.max = max;
      }

      public boolean isValid (Integer t)
      {
            return t <= max;
      }

      public String getMessage ()
      {
            return "число слишком велико";
      }
}

Теперь мы может создавать простые условия, но этого не достаточно. Так как для условия на минимальное и максимальное значение сразу нам придется написать ещё один класс. Это не годится.
Для решения этой проблемы применим паттерн Composite (Компоновщик). Кратко опишем его: Есть несколько классов удовлетворяющих некоторому интерфейсу. Помимо этих классов также могут потребоваться их комбинации, причем они тоже должны удовлетворять тому же интерфейсу. Напишем класс, который был бы контейнером Constraint, причем и сам являлся бы Constraint.

class ComplexConstraint <T> implements Constraint <T>
{
      //Это коллекция условий
      private ArrayList Constraint<T>> <v = new ArrayListConstraint<T>>  ();

      //Первое не пройденное условие. Если все условия пройдены – то null.
      private Constraint<T>  last = null;

      //Добавить условие
      public ComplexConstraint<T>  add (Constraint<T>  c)
      {
            v.add (c);
            return this;
      }

      //Проверить t на корректность
      public boolean isValid (T t)
      {
            for (Constraint<T>  c : v)
            {
                  //Это условие не выполнено, значит сложное условие также не выполнено
                  if (! c.isValid (t))
                  {
                        last = c;
                        return false;
                  }
            }

            //Все условия выполнены
            last = null;

            return true;
      }

      public String getMessage ()
      {
            if (last == null)
                  return "все условия выполнены";
            else
                  return last.getMessage ();
      }     
}

Теперь мы можем сделать:

Constraint <Integer> c = new CompositeConstraint <Integer>().add (new MinValue (18)).add (new MaxValue (100));

А затем в коде использовать c. Но можно сделать проще:

class MinMaxValue extends ComplexConstraint <Integer>
{
      public MinMaxValue (int min, int max)
      {
            add (new MinValue (min));
            add (new MaxValue (max));
      }
}

Теперь приведем результат:

class Person
{
      private Integer age = null;
      private Constrain<Integer>t ageConstraint = new MinMaxValue (18, 100);

      public void setAge (Integer age)
      {
            if (! ageConstraint.isValid (age))
                  System.out.println (ageConstraint.getMessage ());
            …
      }
}

Важно отметить 2 момента:

  • Вы можете прикрепить к ageConstraint дополнительные условия, в том числе и те, которые реализованы, как ComplexConstraint.
  • При невыполненном условии ageConstraint запомнит его и выдаст нужное сообщение об ошибке. Не нужно больше длинных if-else конструкций. Но теперь у нас 2 переменные: age и ageConstraint вместо одной. Это может вам не нравиться. Можно объединить переменную и условия следующим образом:
    class Attribute <T>
    {
          //значение
          private T value = null;
    
          //условие
          private Constraitn<Tt c = null;
    
          public Attribute (T value, Constraint<T>  c)
          {
                this.value = value;
                this.c = c;
          }
    
     //получить значение
          public T get ()
          {
                return value;
          }
    
          //вернуть: удачна ли попытка присвоить значение
          public boolean set (T t)
          {
                if (c.isValid (t))
                {
                      value = t;
                      return true;
                }
    
                return false;
          }
    
          //если было ошибка, вернуть сообщение
          public String getMessage ()
          {
                return c.getMessage ();
          }
    }
    

    После чего использовать в коде:

    Attribute <Integer> age = new Attribute<Integer>(0, new MinMaxValue (18, 100));
    
    if (! age.set (20))
     System.out.println (age.getMessage ());
    

    Если вы не хотите возиться с постоянными get и set, то можете модифицировать MinMaxValue:

    class MinMaxValue extends ComplexConstraint <Integer>
    {
          public MinMaxValue (int min, int max)
          {
                add (new MinValue (min));
                add (new MaxValue (max));
          }
    
          public static boolean isValid (int min, int max, int value)
          {
                return new MinMaxValue (min, max).isValid (value);
          }
    }
    

    После чего использовать в коде:

    if (! MinMaxValue.isValid (18, 100, age))
     System.out.println (“some message”);
    

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

    if (! LengthMinMax.isValid (5, 100, login));
    if (! CorrectCommand.isValid (manager, command));
    if (! CorrectRelativeURL.isValid (base, url));
    

    или так:

          
    LengthMinMax lenMinMax = new LengthMinMax (5, 100);
    if (! lenMinMax.isValid (login));
     
    CorrectCommand correctCommand = new CorrectCommand (manager);
    if (!correctCommand.isValid (command));
    

    и т.д.

  • 2006

    В избранное