Как лучше всего проверить, можно ли поместить объект на другой объект без использования instanceof? - PullRequest
0 голосов
/ 07 августа 2011

Я разрабатываю редактор формы перетаскивания. Однако мне также нужен способ «соединять» фигуры вместе, где только другие фигуры могут быть связаны с другими фигурами. Например, квадрат может быть связан только с кругом, но треугольник может быть связан как с квадратом, так и с кругом.

Итак, я создал суперкласс "Shape", чтобы все другие фигуры были объектами классов, которые наследуются от класса Shape. В классе Shape я поместил метод с именем "canBeConnectedTo (Shape s)", который возвращает, может ли этот конкретный объект быть связан с другим конкретным объектом, но единственный способ увидеть, как это сделать, - использовать оператор instanceof, который приводит мне подумать, что может быть лучший дизайн шаблона.

В настоящий момент класс Square реализует метод следующим образом:

boolean canBeConnectedTo(Shape s) {
    return s instanceof Circle;
}

Я хотел бы сделать это таким образом, чтобы формы, к которым может быть привязана конкретная форма, были параметризованы так, чтобы их можно было добавлять или удалять во время выполнения. Кроме того, он должен быть расширяемым, чтобы можно было легко добавлять новые фигуры без изменения кода.

Альтернатива, которую я придумал, заключается в создании переменной экземпляра в каждом объекте Shape, который содержит тип фигуры, которым является объект, но это не шаблон проектирования, это просто способ избежать использования instanceof без изменения структуры.

Значит, мое мышление изначально ошибочно или нет лучшего способа, чем упомянутая альтернатива?

Ответы [ 3 ]

0 голосов
/ 07 августа 2011

Я бы разделил логику того, какие фигуры могут соединяться друг с другом, в отдельный объект (менеджер соединений), который можно настроить во время выполнения. Shapes должен будет реализовать новый интерфейс IConnectable. Диспетчер подключений можно настроить во время выполнения, чтобы указать, какие подключаемые элементы можно подключить.

Пример кода C # (не проверен):

// The connectable categories don't *need* to be coupled with shape types,
// but you could have a separate category for each shape if required.
public enum ConnectableCategory
{
    Square,
    Triangle
}

// All shapes would implement this interface.
public interface IConnectable
{
    ConnectableCategory Catgetory { get; }
}

// Shapes (or other control logic) would have access to a connection manager
// which determines which connectables can be connected to each other.
public interface IConnectionManager
{
    bool CanConnect(IConnectable first, IConnectable second);
}

// Here's an example impelentation of a connection manager.
public class ConnectionManager : IConnectionManager
{
    private Dictionary<ConnectableCategory, HashSet<ConnectableCategory>>
        permittedConnections =
        new Dictionary<ConnectableCategory, HashSet<ConnectableCategory>>();

    // Configure the connection manager to permit connections between specified
    // categories of connectables.
    public void PermitConnection(
        ConnectableCategory first,
        ConnectableCategory second)
    {
        HashSet<ConnectableCategory> permittedTargets;
        if (this.permittedConnections.TryGetValue(first, out permittedTargets))
        {
            permittedTargets.Add(second);
        }
        else
        {
            permittedTargets = new HashSet<ConnectableCategory>() { second };
            this.permittedConnections.Add(first, permittedTargets);
        }
    }

    // Test if two connectables can be connected.
    public bool CanConnect(IConnectable first, IConnectable second)
    {
        HashSet<ConnectableCategory> permittedTargets;
        if (this.permittedConnections.TryGetValue(
            first.Catgetory,
            out permittedTargets))
        {
            return permittedTargets.Contains(second.Catgetory);
        }
        else
        {
            return false;
        }
    }
}
0 голосов
/ 07 августа 2011

Вот схема, которая:

  • не использует instanceof или эквиваленты.
  • фигуры, к которым может быть добавлена ​​или удалена конкретная фигура, могут быть добавлены или удаленыво время выполнения.

    public class Shape {
          public Map<Type, Boolean> connections = new EnumMap<Type, Boolean>(Type.class);
    
          public Boolean canConnectTo(Shape shape) {
                Boolean canConnect = connections.get(shape.getType());
    
                if (canConnect == null)
                  canConnect = false;
    
                return canConnect;
          }
    }
    
    
    public class Circle extends Shape {
          public Circle() {
                connections.put(typeof(Square), true);
                connections.put(typeof(Triangle), false);
          }
    }
    
    
    public class Square extends Shape {
          public Square() {
                connections.put(typeof(Circle), true);
                connections.put(typeof(Triangle), true);
          }
    }
    
    public class Triangle extends Shape {
          public Triangle() {
                connections.put(typeof(Circle), false);
                connections.put(typeof(Square), true);
          } 
    }
    

EnumMap connections отображает возможные связи между фигурами.Если ключ, сопоставленный с определенным типом фигуры, либо отсутствует, либо явно ложен, соединение не допускается, в противном случае две фигуры (текущая и полученная в качестве параметра) могут соединиться.

As connections является общедоступным, его можно изменять в каждом экземпляре каждого подкласса Shape, поэтому вы можете решить во время выполнения, какие фигуры могут соединяться, а какие нет.

0 голосов
/ 07 августа 2011

Я хотел бы сделать это таким образом, чтобы формы, к которым может быть привязана конкретная форма, были параметризованы так, чтобы их можно было добавлять или удалять во время выполнения.

В этом случае проверка типов представляется необходимой.Класс Class имеет методы isAssignableFrom и isInstance, которые являются эквивалентами отражения для instanceof, или просто используйте метод equals, если не нужно принимать во внимание подтипы (в любом случае, рекомендуетсяизбегать расширения конкретных классов).Используя их, вы можете иметь один класс, который отслеживает, какие фигуры могут соединяться с какими фигурами (например, используя экземпляры Map of Class), и, таким образом, иметь эту логику в одном месте, а не распространяться на все подклассы Shape.

Это может быть сделано с чем-то вроде этого, предполагая, что подклассы не используются, поэтому мы можем просто сравнить классы на равенство.(Отказ от ответственности: составлено, не проверено)

import java.util.*;

public class ConnectionPermissions {

    private final Set<Connection> allowed = new HashSet<Connection>();

    public void allowConnection(Class<?> from, Class<?> to) {
        allowed.add(new Connection(from, to));
    }

    public void disallowConnection(Class<?> from, Class<?> to) {
        allowed.remove(new Connection(from, to));
    }

    public boolean isConnectionAllowed(Class<?> from, Class<?> to) {
        return allowed.contains(new Connection(from, to));
    }

    // Could also use a method like this, to hide the detail that permissions
    // are based on the types of the objects.
    public boolean isConnectionAllowed(Object from, Object to) {
        return isConnectionAllowed(from.getClass(), to.getClass());
    }

    private static class Connection {
        private final Class<?> c1;
        private final Class<?> c2;

        public Connection(Class<?> c1, Class<?> c2) {
            // Remove this condition if the connections should not be symmetric.
            if (c1.getName().compareTo(c2.getName()) < 0) {
                this.c1 = c2;
                this.c2 = c1;
            } else {
                this.c1 = c1;
                this.c2 = c2;
            }
        }

        public boolean equals(Object obj) {
            Connection that = (Connection) obj;
            return this.c1.equals(that.c1) && this.c2.equals(that.c2);
        }

        public int hashCode() {
            int result = c1.hashCode();
            result = 31 * result + c2.hashCode();
            return result;
        }
    }
}
...