设计模式问题

4

我有一个问题需要优雅地解决:

public interface IMyclass
{
} 
public class A
{
   public void Init(IMyclass class){?}
   public IMyclass CreateMyClass(){?}
}

在系统开始时,我想通过使用Init()定义IMyClass的动态类型,在系统运行期间,我希望创建我在init中定义的类型的新实例。

注:
1. IMyclass必须是接口
2. IMyclass的动态类型仅在init中已知(我没有构造函数)
3. 我可以使用反射或定义方法克隆来完成,但是否有更好的解决方案?

谢谢。


你可能需要注意到,你的编码规范是非标准的。在Java中,I表示接口声明和方法名称的首字母大写通常不使用(有些人会争辩接口声明这个问题,但Eclipse是我见过唯一使用它的主要代码)。 - Robin
4个回答

2
您可以将一个提供程序传递到 A 类 中。
public class A
{ 
   IMyClassProvider _provider;

   public void Init(IMyClassProvider provider) 
   {
        _provider = provider; 
   }

   public IMyclass CreateMyClass()
   {
        return _provider.Create();
   }
}

也许可以使用构造函数委托来实现。
public class A
{ 
   Func<IMyclass> _ctor;

   public void Init(Func<IMyclass> ctor) 
   {
        _ctor = ctor; 
   }

   public IMyclass CreateMyClass()
   {
        return _ctor();
   }
}

请注意,如果在调用CreateMyClass之前未调用Init,这两个示例都会出错,您需要进行一些检查或更好的方法是在构造函数中执行初始化操作。
我是否正确理解了问题?

你能否解释一下提供者或供应使用代码是什么意思? - Sergey Kucher
我现在刚下班,如果你那时还没有解决问题,我稍后会更新。 - fearofawhackplanet
我刚刚意识到你的问题被标记为Java,而我使用了C#代码进行回答。在Java中应该可以应用类似的模式,但是我对语法不是很熟悉。 - fearofawhackplanet
我理解了 :) 但是你的解决方案是语言无关的,在Java中,你可以使用命令模式进行委派。谢谢。 - Sergey Kucher

2
这是一种依赖注入技术,您应该阅读以下内容: 基本上,你有一个类A,在初始化时用工厂(或提供者)填充。然后你使用A来代替调用new。
一个快速的例子:
interface Provider<V> {
   V instance(Object... args);
}
class Dispatch {
   // you can make a singleton out of this class
   Map<Class, Provider> map;
   <T> void register(Class<T> cl, Provider<? extends T> p) {
      // you can also bind to superclasses of cl
      map.put(cl, p);
   }
   <T, I extends T> void register(Class<T> cl, final Class<I> impl) {
      register(cl, new Provider<I>() {
         I instance(Object... args) {
            // this class should be refactored and put in a separate file
            // a constructor with arguments could be found based on types of args values
            // moreover, exceptions should be handled
            return impl.newInstace();
         }
      });
   }
   <T> T instance(Class<T> cl, Object... args) {
      return map.get(cl).instance(args);
   }
}

// usage
interface MyIf { ... }
class MyIfImpl implements MyIf { ... }

Dispatch d = new Dispatch();
d.register(MyIf.class, new Provider<MyIf>() {
   MyIf instance(Object... args) {
      return new MyIfImpl();
   }
});
// or just
d.register(MyIf.class, MyIfImpl.class);
MyIf i = d.instance(MyIf.class);

Edit: added register(Class, Class)


非常感谢,您提供了一个出色的解决方案,我从中学到了很多,但我想避免使用反射。 - Sergey Kucher
如果你想不使用反射来实现它,只需删除函数register(Class, Class),这个函数可能很方便,但并不是很类型安全。 - Kru

1

如果您只想在CreateMyClass()中实例化相同的类而无需进一步配置,则可以使用反射。

public class A
{
    private Class prototype;

    public void Init(IMyClass object) {
        this.prototype = object.getClass();
    }

    public IMyClass CreateMyClass() {
        return prototype.newInstance();
    }
}

我猜你想要更多的东西,如果是这样的话,你需要解释一下想要如何使用它。你可能正在寻找Builder或者Factory模式。


谢谢,这解决了问题。但是你能告诉我关于性能问题怎么办吗?我经常创建这个参数。如果它会导致性能问题,你有没有不涉及反射的解决方案? - Sergey Kucher
如果你想在运行时根据另一个类的类来动态实例化对象,不行。但是,如果你只有少数这样的类需要这样做,那么你可以像fearofawhackplanet所演示的那样为每个类编写单独的工厂。 - David Harkness

1

由于可见性问题,您在某个时候需要使用反射。如果您可以接受一次性地使用反射而不必再次使用它,那么这可能是理想的,对吧?

您可以在一个隐藏的接口上放置一个getInstance()方法(位于与IMyClassMyClassImplA相同的包中,但不包括ClientOfA),然后将MyClassImpl的原型传递给A.init()

// -- You wish you would have thought of the word prototypeable! ...maybe?
interface IMyClassPrototypeable extends IMyClass
{
 public IMyClass getInstance();
}

class MyClassImpl implements IMyClassPrototypeable // -- and IMyClass by extension.
{
 // -- Still not visible outside this package.
 public IMyClass getInstance()
 {
  return new MyClassImpl();
 }
}

class A
{
 private IMyClassPrototypeable prototype;

 // -- This method is package-private.
 void init( IMyClassPrototypeable prototype )
 {
  this.prototype = prototype;
 }

 public IMyClass createMyClass()
 {
  return prototype.getInstance();
 }
}

这个解决方案需要使用反射来创建MyClassImpl的原型实例,可以通过Spring(或其他形式的依赖注入)来完成。它使用了原型模式、工厂方法模式,并且支持单例/池模式,但请记住,使用更多的设计模式并不总是更好的。事实上,它可能会使设计(和代码)更加复杂,更难以理解。

值得一提的是,我甚至考虑支持这个解决方案的唯一原因是,它只在最开始时承担了反射开销,而不是每次调用createMyClass()时都要承担,而原始帖子中指出他/她会经常这样做。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接