【问题标题】:Instance members of singleton object or static members of class单例对象的实例成员或类的静态成员
【发布时间】:2013-12-09 10:14:10
【问题描述】:

我正在编写一个监听器类,它随机创建一个 HttpListener 未使用的端口并监听它。我试图使它成为单例。然而我是 收听时发现很难保留那个单例对象null 到随机 HTTP 端口失败 10 次。

(我已将端口号和 HttpListener 实例显示为单例的成员 实例。但是还有很多其他成员我没有展示 简单。)这是我的代码:

class Listener
{
    private static Listener listener = null;  //singleton instance

    //member variables
    private HttpListener httpListener = null;
    private int port = -1;

    static Listener()
    {
        listener = new Listener();
    } 

    private Listener()
    {
        try
        {
            port = //randomly generate
            httpListener = new HttpListener();
            //start listening
        }   
        catch(Exception ex)
        {
            //cant listen on randomly chosen port
            listener = null;
        httpListener = null;
            port = -1;
            return;            
        }   
    }
}

但是在 catch()listener = null 内部将 listener 设置为 null 只是一会儿。当默认构造函数返回时,它返回一个新的 Listener 的实例,因此静态构造函数中的 listener 总是有 分配给它的实例。因此,而不是 listener = null 默认内部 构造函数应该是this = null,这是无效的。

因此,我将整个代码移到了静态构造函数中。但这迫使我

  • 要么创建实例成员(porthttpListenerstatic 要么
  • 或在任何地方使用listener.portlistener.httpListener

    类监听器 { 私有静态侦听器侦听器= null; //单例实例

    //member variables
    private HttpListener httpListener = null;
    private int port = -1;
    
    static Listener()
    {
        listener = new Listener();
    
    try
        {
        listener.port = //randomly generate
            listener.httpListener = new HttpListener();
            //start listening
        }   
        catch(Exception ex)
        {
            //cant listen on randomly chosen port
        listener = null;       
            return;            
        } 
    } 
    
    private Listener()
    {  
    }
    

    }

我不明白

Q1是否制作port&httpListenerstatic? (这我觉得有点违反 OOP 原则)或

Q2是否将它们保留为实例成员并在任何地方使用listener.? (这是有问题的,因为我的实际代码中有很多这样的成员和方法,我必须在任何地方附加listener.

或者我认为这一切都错了,应该采用不同的方法?

【问题讨论】:

  • 你想破解一个端口吗?
  • why singletons are evil。在您所描述的上下文中,您似乎并不需要一个单例。
  • @BartoszKP 哦……这到底是怎么回事?
  • 这真的很奇怪,你想完成什么?尝试avoid the XY problem
  • @Mahesha999 问题是在这里强制使用单例。您真正拥有的是抽象工厂模式,而不是单例模式。

标签: c# java oop design-patterns singleton


【解决方案1】:

直接的问题是实例构造函数在失败时将静态成员侦听器设置为空。但是,控制权随后返回到静态构造函数,该构造函数将静态成员侦听器设置为在实例构造函数中创建的对象。因此,您看到的行为。

我认为您的(实例)构造函数试图做的太多了。我会将“开始监听”逻辑移动到一个单独的方法中,并从实例构造函数以外的任何地方调用它。这将使您的错误处理更容易一些,例如

class Listener
{
    public static Listener listener = null;  //singleton instance

    //member variables
    private HttpListener httpListener = null;
    private int port = -1;

    static Listener GetListener()
    {
        if (listener != null)
        {
            return listener;
        }

        try
        {
            listener = new Listener();
            listener.StartListening();
            return listener;
        }
        catch (Exception)
        {
            //cant listen on randomly chosen port
            listener.Cleanup();
            listener = null;
            throw;
        }
    }

    private Listener()
    {
        port = RandomlyGenerate();
        httpListener = new HttpListener();
    }

    private void StartListening()
    {
        //start listening
    }

    private void Cleanup()
    {
        httpListener.Close();
        httpListener = null;
        port = -1;
    }
}

【讨论】:

  • 所以这也必须包含 static Listener getListener() 和我必须做的 Listener.getListener().StartListening()
  • 是的。我已经用更好的静态 GetListener() 替换了静态构造函数。上面的代码是说明性的,例如它不是线程安全的,GetListener() 可以尝试侦听多个端口,直到找到一个有效的端口,并且可能没有必要在每次尝试时都清理 HttpListener。
  • 但是取消listener 怎么样?或者为什么取消static singleton 是个坏主意?
  • 您的主要用例类似于 Listener.GetListener().DoSomething。但是,如果 GetListener() 返回 null,那么当系统尝试调用 DoSomething() 时,您将得到一个 null 引用异常。这需要每次都检查 GetListener() 的返回值,这不是最好的设计。
  • 哦,我想到了:拥有void init(),它将创建httpListenerport,然后开始收听。我将在static EwsListener() 中调用listener.init() 这样我就不必在任何地方附加listener.,也不需要显式调用init(),我也不会设置listener = null,这样getListener() 将始终返回实例。
【解决方案2】:

正如 BartoszKP 在 cmets 中提到的那样,在这里使用工厂模式可能会更好。

如果您有兴趣为事件提供一个公共位置,您可以将该事件放在工厂类中,或者在 Listener 类中实现一个静态事件。

public class ListenerFactory {

    public IListener CreateListener(URI uri, int port) {
        Listener l = new Listener();
        l.MessageReceived += OnMessageReceived;
        // do whatever with l. loop until connection, or use l.Start() for instance
        return l;
    }

    public static event EventHandler<MessageEventArgs> ListenerMessageReceived;

    private static void OnMessageReceived(object sender, MessageEventArgs e) {
        // trigger ListenerMessageReceived
    }
}

public interface IListener {
    event EventHandler<MessageEventArgs> MessageReceived;
    void Send(byte[] data);
}

public class Listener : IListener {
    // implement interface
}

然后,当您需要一个新的 Listener 时,您只需调用 new ListenerFactory().Create(host, port);,如果您想侦听所有消息,则订阅 ListenerFactory.MessageReceived 以接收传入消息。

使用此模式,您可以一次创建多个连接,而不是依赖一个类来处理所有连接。


您应该将 httpListener 的初始化代码移到它自己的方法中,以避免重新创建侦听器。通过这样做,并添加一个用于获取侦听器实例的属性,如果静态构造函数连接失败,类可以使用Listener.Instance.Start() 重新连接。

public class Listener
{
    private static Listener listener = null;  //singleton instance

    //member variables
    private HttpListener httpListener = null;
    private int port = -1;

    static Listener()
    {
        listener = new Listener();
        // start listener
        try {
             listener.Start();
         }
         catch { }
    }

    // Use this method in other classes to start listener if it fails
    // in static constructor
    public static Listener Instance { get { return listener; } }

    private Listener()
    {
    }

     public bool IsConnected {
         get { return httpListener != null; }
     }

     public void Start() 
     {
        if (IsConnected) { return; }
        try
        {
            port = //randomly generate
            httpListener = new HttpListener();
            //start listening
        }   
        catch(Exception ex)
        {
            //cant listen on randomly chosen port
        httpListener = null;
            port = -1;
            return;            
        }   
    }
}

【讨论】:

  • 嘿,你将httpListener 设置为null。我不应该将listener 设置为null。或者只是我们不应该将static 实例设置为null
  • 是的,静态实例永远不会设置为null,你只更新实例变量httpListener
  • 为什么取消static singleton 是个坏主意?
  • 静态对象可以在项目内的很多地方使用。当静态对象设置为 null,而其他代码使用它时,可能会导致异常。它可能导致竞争条件并使其更难调试(查找对象设置为 null 的原因)。
  • 例如,以下代码会导致问题:Listener l = Listener.Instance; l.DoSomething(); 如果在调用之间将静态实例设置为 null,您最终可能会出现您不想要的状态,即“松散”变量”,因为变量 l 存储在侦听器类控制之外的本地状态中。
猜你喜欢
  • 1970-01-01
  • 2015-08-23
  • 1970-01-01
  • 1970-01-01
  • 2019-01-10
  • 2010-10-17
  • 2012-12-15
  • 1970-01-01
相关资源
最近更新 更多