无法使用多态性访问具体类的唯一属性

Posted

技术标签:

【中文标题】无法使用多态性访问具体类的唯一属性【英文标题】:Cannot access the unique properties of a concrete class using polymorphism 【发布时间】:2014-07-16 10:42:43 【问题描述】:

我在java version "1.7.0_60"中使用工厂模式创建不同连接的对象

我面临的问题是每个具体类都将具有该特定类的独特属性。由于工厂在返回具体类的实例时将使用多态性,因此我无法访问唯一属性。即 getHostType() 仅对 SqlServerConnection 是唯一的。

我所做的解决方法是在超类中声明getHostType() abstract 并在每个具体类中实现它。但是,我真的不想这样做,因为我添加的具有其独特属性的具体类越多,我必须在超类中包含的抽象方法就越多,然后在每个具体类中实现它们。

我想保留我的工厂模式和抽象超类。我只是想知道是否有其他方法可以代替超类中的抽象方法?我可以包含任何设计模式来解决这个问题吗?

public abstract class Connection 
    private int port;
    private int ipAddress;

    public Connection() 

    public String description() 
        return "Generic";
    

    /* Implement in every concrete class, even if the concrete type doesn't have that property */
    public abstract int getHostType();


public class SqlServerConnection extends Connection 
    private int sqlHostType;

    public SqlServerConnection() 
        sqlHostType = 5060;
    

    @Override
    public String description() 
        return "Created a Sql Server connection type";
    

    @Override
    public int getHostType() 
        return sqlHostType;
    


public class OracleConnection extends Connection 
    public OracleConnection() 

    @Override
    public String description() 
        return "Created an Oracle connection type";
    


final public class ConnectionFactory 
    protected String mType;

    public ConnectionFactory(String type) 
        mType = type;
    

    /* Create the connection we want to use */
    public Connection createConnection() 
        if(mType.equals("Oracle")) 
            return new OracleConnection();
        
        else if(mType.equals("SQLServer")) 
            return new SqlServerConnection();
        
        else 
            return null;
        
    


public class TestConnection 
    public static void main(String[] args) 
        ConnectionFactory factory = new ConnectionFactory("SQLServer");
        Connection conn = factory.createConnection();

        conn = factory.createConnection();
        System.out.println(conn.description());
        /* need to access the getHostType() */
        System.out.println(conn.getHostType());
    

【问题讨论】:

我认为你要么使用没有抽象的工厂,要么使用没有抽象的工厂来解决你的问题,因为你是如何做到的,你可能需要一些铸造。您还可以检查抽象工厂模式。 您似乎正试图将设计模式强加到一个并不真正适合它的领域。首先,您尝试抽象所有内容,然后您发现无论如何都需要具体类型。这可以通过强制转换来完成,但是为什么不直接显式声明具体类的变量并通过调用具体构造函数来实例化它们呢?工厂模式在这里添加了哪个值?我只看到一个“绕圈子”的模式…… 为什么需要访问这些属性? 您能告诉我们 Connection 类的功能,即您想对 Connection 类型做什么,以便我们清楚地了解需要什么以及如何解决。根据提供的信息,我们无法做出任何决定 如果getHostType()是特定于SqlServer的,为什么需要在特定于SqlServer的Connection之外访问它? 【参考方案1】:

您应该看看访问者模式。您需要声明一个接口 ConnectionVisitor 并为层次结构中的每个连接类添加一个方法访问。

public interface ConnectionVisitor 

    public int visit (Connection connection);
    public int visit (SqlServerConnection sqlconnection);
    public int visit (OracleConnection oracleConnection)

现在您需要在您的基类连接中添加一个接受方法,该方法接受一个 ConnectionVisitor,然后对其调用访问。您的新 Connection 类将类似于

public abstract class Connection 
  private int port;
  private int ipAddress;

  public Connection() 

  public String description() 
     return "Generic";
  

  public int accept(ConnectionVisitor visitor)
     return visitor.visit(this);
  

请注意,accept 方法执行双重调度。它基于调用它的对象和传递给此方法的参数进行调度。这是访问者模式的核心。

然后您可以实现 ConnectionVisitor 接口来定义任何新功能,而无需更改您的基类。

class   DemoVisitor implements ConnectionVisitor
  public  int visit(Connection connection)
     System.out.println("Visiting Connection");
     return 1;
  

  public int visit(SqlServerConnection sqlServerConnection)
     System.out.println("Visiting SqlServerConnection");
     return 1;
  

  public  int visit(OracleConnection oracleConnection)
     System.out.println("Visiting Oracle Connection");
     return 1;
  

在您的 TestConnection 类中,您可以简单地创建一个新的连接对象,然后对该对象调用 accept 方法并传递一个访问者对象。

public class TestConnection 
  public static void main(String[] args) 
    ConnectionFactory factory = new ConnectionFactory("SQLServer");
    Connection conn = factory.createConnection();

    conn = factory.createConnection();
    System.out.println(conn.description());
    ConnectionVisitor visitor = new DemoVisitor();
    System.out.println(conn.accept(visitor));
  

因此,现在任何子类特定功能都不能驻留在连接类层次结构中,而是必须在新访问者中实现。

请注意,此模式不适合您的场景。此模式的限制之一是访问者界面中所有方法的返回类型必须相同。这种模式可能符合您的需求,也可能不符合您的需求,但值得研究您的情况。您可能需要修改此模式以满足您的需要。这就是模式的全部意义在于寻找一些常见的解决方案,然后修改这些解决方案以适应您的问题。

【讨论】:

谢谢,我一直在寻找使用设计模式的解决方案。我只是需要时间来看看这个。 这种方法的问题是,如果你想添加一个新的连接子类型,你必须改变已经存在的访问者。这意味着重新编译。此外,访问者模式更多地用于外包对对象的操作,而不是用于数据查找。但这是一个有趣的解决方案,您应该添加泛型以支持多种返回类型 :) ...我仍然更喜欢我的解决方案 :P 就重新编译而言,我们可以使用访问者模式的修改,称为非循环访问者,当添加新的连接子类型时,不需要重新编译整个层次结构。而且使用泛型实际上是个好主意。:) java 中不存在双重调度。您的代码不起作用(方法public int visit (Connection connection); 将始终被调用),除非您在每个连接子类中覆盖accept 方法。 你是对的,你需要在每个连接子类中重写接受方法。我忘了补充。但这仍然是双重调度。来自 Robert C. Martin 在 java 中访问者的描述“这被称为双重调度,因为它涉及两个多态调度。第一个是接受函数。这个调度解析了调用接受的对象的类型。第二个调度是访问从解析的接受方法调用的方法。第二个调度解析到要执行的特定函数。 objectmentor.com/resources/articles/visitor【参考方案2】:

你为什么想要那个?我的意思是,我使用工厂来隐藏分解对象的特定实现,返回一个通用的抽象类(或接口)。

我想知道你为什么想做这样的事情:

ConnectionFactory factory = new ConnectionFactory("SQLServer");
Connection conn = factory.createConnection();    
if(conn.getHostType() == 1 ) 
   doSomethingLogic();

if(conn.getHostType() == 2) 
   doSomethingElseLogic();

不应该所有的ifs都在工厂里面吗?

【讨论】:

我必须同意丹的观点。 OP,Why do you want that? 为什么要检索所有唯一属性?【参考方案3】:

您需要在 Connection 类中包含 getHostType() 方法,才能多态调用此方法。 唯一的其他解决方案是将工厂返回的对象类型转换为预期的对象,这根本不是一个好方法。这样做的原因是您必须通过 if else 语句检查返回对象的 Class 类型,无论它是 ORACLE 还是 mysql 等(我们有不必要的多态性来防止这种情况)。如果您只是在 Connection 类中定义方法,则无需担心 getHostType() 方法,因为它将从正确的类中以多态方式调用。

oracleConnection 类中,您只需添加方法getHostType(),该方法在您编写的代码中返回带有错误消息的空对象。

【讨论】:

【参考方案4】:

我不知道这种方法对您的具体情况有多适用,但您可以尝试将相关的、可选的、特定于子类的连接行为分组到自己的接口中,然后让每个具体类实现适合的接口它。

在您的示例中,两个连接类都实现了 description(),因此您可以创建一个名为 Descriptor 的接口,并在您的抽象类上有一个名为 getDescriptor() 的方法:

public Descriptor getDescriptor() throws HasNoDescriptorException 

   if (self instanceof Descriptor) 
        return self;
    

    throw new HasNoDescriptorException();

 

然后让接口Descriptor提供description()方法。

你会得到这样的连接器描述:

String desc = "";
try 

    desc = connector.getDescriptor().description();

 catch (HasNoDescriptorException e) 

    // connector doesn't have a description() method;


如果您不喜欢异常,您可以返回并测试空值。

就此而言,您的代码可以简单地测试以查看连接实例是否是 Descriptor 接口的实例,如果是,那么您知道您可以完全访问适用于 Descriptor 的任何方法。

继续这个例子,你可以有一个 TypedHost 接口,它的实现连接类提供了一个 getSqlHost() 方法。

【讨论】:

【参考方案5】:

我会这样做:

从抽象类中删除getHostTypeCreate 方法,因为并非所有连接都具有此属性。然后添加一个新接口IHostTypeProvider(不过你可能会选择一个更好的名字):

public interface IHostTypeProvider 
   int getHostType();

现在,让一些子类实现这个接口:

public SqlServerConnection extends Connection implements IHostTypeProvider 
....
  public int getHostType() 
     return 5060;
  

当您真正需要访问该属性时,您首先需要检查它是否可用于这种类型的连接:

Connection con = ...;
//Check if host type is available
if (connection instanceof IHostTypeProvider) 
  System.out.println(((IHostTypeProvider)con).getHostType());

希望对您有所帮助。

【讨论】:

通过简单性实现可维护性 - 在处理数据库连接时非常重要。【参考方案6】:

听起来像是类型安全的异构容器的用例。我将发布我的示例,我认为它几乎可以解释自己。如果还有什么问题,我会一一解答。

优点是易于扩展并且支持多种类型。

import java.util.HashMap;
import java.util.Map;

public abstract class Connection

  /* typesafe heterogeneous container */
  private final Map<Property<?>, Object> properties = new HashMap<Property<?>, Object>();

  public Connection(String ip, int port)
  
    addProperty(ConnectionProperties.IP_ADDRESS, ip);
    addProperty(ConnectionProperties.PORT, port);
  

  /**
   * Gets property in its respective type.
   *
   * @param p property
   * @return value of property.
   */
  public <T> T getProperty(Property<T> p)
  
    Object obj = properties.get(p);
    if (obj == null)
      return null;
    Class<T> clazz = p.getClazz();
    return clazz.cast(obj);
  

  /**
   * Checks whether property is available
   *
   * @param p property to check for
   * @return <code>true</code>, if property is available
   */
  public boolean hasProperty(Property<?> p)
  
    return properties.get(p) != null;
  

  /* helper method to add properties */
  protected <T> void addProperty(Property<T> p, T value)
  
    properties.put(p, value);
  


class SqlServerConnection extends Connection

  public SqlServerConnection(String ip, int port)
  
    super(ip, port);

    addProperty(ConnectionProperties.DESCRIPTION, "Created a Sql Server connection type");
    addProperty(ConnectionProperties.SQL_HOST_TYPE, 5090);
  


/* all properties are stored here (...there could be more classes if needed) */
final class ConnectionProperties

  private ConnectionProperties()
  
    // private contructor to prevent instantiation of utility class
  

  public static final Property<String> IP_ADDRESS = new Property<String>("IP_ADDRESS", String.class);
  public static final Property<Integer> PORT = new Property<Integer>("PORT", Integer.class);

  public static final Property<String> DESCRIPTION = new Property<String>("DESCRIPTION", String.class);

  public static final Property<Integer> SQL_HOST_TYPE = new Property<Integer>("SQL_HOST_TYPE", Integer.class);


/* property class that serves as key for typesafe heterogeneous container */
final class Property<T>

  /* has to be unique */
  private final String name;
  private final Class<T> clazz;

  public Property(String name, Class<T> clazz)
  
    this.name = name;
    this.clazz = clazz;
  

  public String getName()
  
    return name;
  

  public Class<T> getClazz()
  
    return clazz;
  

  @Override
  public int hashCode()
  
    final int prime = 31;
    int result = 1;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    return result;
  

  @Override
  public boolean equals(Object obj)
  
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    Property<?> other = (Property<?>) obj;
    if (name == null)
    
      if (other.name != null)
        return false;
    
    else if (!name.equals(other.name))
      return false;
    return true;
  

您还可以为Property&lt;T&gt;Connection 定义一个接口,这将具有交换实现的可能性的未来好处,但我在这里留出一些空间。


如果属性的实例很复杂,还有其他选择。比如下面的

public final class PropertyV2<T>

  private static final AtomicInteger KEY_SUPPLY = new AtomicInteger();

  /* unique key for property distinction */
  private final int key;
  private final Class<T> clazz;

  private PropertyV2(Class<T> clazz)
  
    this.key = KEY_SUPPLY.getAndIncrement();
    this.clazz = clazz;
  

  /* factory method for string properties */
  public static PropertyV2<String> string()
  
    return new PropertyV2<String>(String.class);
  

  /* factory method for integer properties */
  public static PropertyV2<Integer> integer()
  
    return new PropertyV2<Integer>(Integer.class);
  

  public Class<T> getClazz()
  
    return clazz;
  

  @Override
  public int hashCode()
  
    final int prime = 31;
    int result = 1;
    result = prime * result + key;
    return result;
  

  @Override
  public boolean equals(Object obj)
  
    if (obj == null || getClass() != obj.getClass())
      return false;
    PropertyV2<?> other = (PropertyV2<?>) obj;
    if (key != other.key)
      return false;
    return true;
  


class ConnectionPropertiesV2

  private ConnectionPropertiesV2()
  
    // private constructor to prevent instatiation of utiltiy class
  

  PropertyV2<String> IP_ADDRESS = PropertyV2.string();
  PropertyV2<Integer> PORT = PropertyV2.integer();

这里的问题是,您丢失了 name 属性,如果您想在运行时使用属性名称,这可能很有用 - 假设在异常中。

【讨论】:

我用java写了一个属性管理器,也许你可以看看。应该帮助你:bitbucket.org/mbeyene/jam。还有代码示例。 这个api不流畅。而通过动态定义属性,你失去了编译的优势 @gontard 一个人怎么能创造……流利的The problem I am facing is that each concrete class will have unique properties for that particular class。我没有看到任何通用的、流畅的方法来解决这个问题。【参考方案7】:

更多的OO方法是将实现细节推送到子类中,而不是在抽象方法中公开它们(对于某些子类可能没有明确定义的实现)。

例如,而不是写作,

    System.out.println(conn.description());
    /* need to access the getHostType() */
    System.out.println(conn.getHostType());

改为写

    conn.printTo(System.out);

然后为每个孩子提供一个 printTo 方法。通过这种方式,您重构了代码以隐藏 Connection 对象的所有实现细节以及它如何将自身打印到流中。

【讨论】:

以上是关于无法使用多态性访问具体类的唯一属性的主要内容,如果未能解决你的问题,请参考以下文章

C++之封装继承和多态

封装继承和多态

JavaSE12-封装继承和多态

无法访问类的公共属性

java三大特性封装继承多态

实验五——类的多态,继承和派生2