与相关类共享类的实例

时间:2018-12-21 14:35:08

标签: c# class inheritance

我有一组从ConfigurationSection类继承的类。我还有一个Logger类,可用于为每个类创建单独的日志文件,但这还不包括这些配置类。

我希望为每个Logger类创建一个新的ConfigurationSection实例(因此,创建一个新的日志文件),我希望它们全部使用相同的实例并登录到相同的文件。

实现此目标的最佳方法是什么?

以下是我考虑过的几种选择。


实例化每个相关类时,将未记录的Logger作为参数传递

这似乎是最明显的选择,但我真的不喜欢它。我的Logger类不是完全轻量级的,我希望避免传递这么大的参数。无论如何,这是一个选择。

class Program
{
    static void Main( string[] args )
    {
        // These variables are used for illustrative purposed,
        // instead of showing my `Logger' class
        bool loggerInstantiated = true;
        DateTime InstantiatedTime = DateTime.Now;

        DerivedClass01 dc01 = new DerivedClass01( loggerInstantiated, InstantiatedTime );

        // added to create delay between the instantiation of different derived classes
        System.Threading.Thread.Sleep( 1000 );

        DerivedClass02 dc02 = new DerivedClass02( loggerInstantiated, InstantiatedTime );
    }
}


class BaseClass : ConfigurationSection
{
    public BaseClass( bool loggerInstantiated, DateTime instantiatedAt )
    {
        Console.WriteLine(string.Format("BaseClass:\n\r\tLoggerInitiated is {0} at {1}\n\r", loggerInstantiated, instantiatedAt ) );
    }
}

class DerivedClass01 : BaseClass
{ 
    public DerivedClass01( bool loggerInstantiated, DateTime instantiatedAt ) : base(loggerInstantiated, instantiatedAt)
    {
        Console.WriteLine( string.Format( "DerivedClass01:\n\r\tLoggerInitiated is {0} at {1}\n\r", loggerInstantiated, instantiatedAt ) );
    }
}

class DerivedClass02 : BaseClass
{
    public DerivedClass02( bool loggerInstantiated, DateTime instantiatedAt ) : base( loggerInstantiated, instantiatedAt )
    {
        Console.WriteLine( string.Format( "DerivedClass02:\n\r\tLoggerInitiated is {0} at {1}\n\r", loggerInstantiated, instantiatedAt ) );
    }
}

输出:

/*
BaseClass:
    LoggerInitiated is True at 21/12/2018 14:16:09

DerivedClass01:
    LoggerInitiated is True at 21/12/2018 14:16:09

BaseClass:
    LoggerInitiated is True at 21/12/2018 14:16:09

DerivedClass02:
    LoggerInitiated is True at 21/12/2018 14:16:09
*/

结论:

这当然是一个选择。从BaseClass派生的每个类都可以使用相同的Logger。但是由于我的Logger类的大小,我认为最好不要将整个实例作为参数传递。

编辑:

正如@Malior和@ScottHannen指出的那样,当您将类的实例作为参数时,我完全误解了实际情况。我现在不想这样做的原因在某种程度上是无效的,但是我仍然宁愿在实例化从Logger派生的类时也不必传递BaseClass作为参数。


从基类继承

创建一个继承自BaseClass的{​​{1}},并在ConfigurationSection上实例化Logger。任何相关的BaseClass类都可以从此ConfigurationSection继承。

代码:

BaseClass

输出:

class Program
{
    static void Main( string[] args )
    {
        DerivedClass01 dc01 = new DerivedClass01();

        // added to create delay between the instantiation of different derived classes
        System.Threading.Thread.Sleep( 1000 );

        DerivedClass02 dc02 = new DerivedClass02();
    }
}

class BaseClass : ConfigurationSection
{
    // These variables are used for illustrative purposed,
    // instead of showing my `Logger' class
    public bool LoggerInstantiated;
    public DateTime InstantiatedAt;

    public BaseClass()
    {
        LoggerInstantiated = true;
        InstantiatedAt = DateTime.Now;

        Console.WriteLine(string.Format("BaseClass:\n\r\tLoggerInitiated is {0} at {1}\n\r", LoggerInstantiated, InstantiatedAt));
    }
}

class DerivedClass01 : BaseClass
{
    public DerivedClass01()
    {
        Console.WriteLine( string.Format( "DerivedClass01:\n\r\tLoggerInitiated is {0} at {1}\n\r", LoggerInstantiated, InstantiatedAt ) );
    }
}

class DerivedClass02 : BaseClass
{
    public DerivedClass02()
    {
        Console.WriteLine( string.Format( "DerivedClass02:\n\r\tLoggerInitiated is {0} at {1}\n\r", LoggerInstantiated, InstantiatedAt ) );
    }
}

结论:

这不好。每次实例化派生类时都会创建一个新实例/* BaseClass: LoggerInitiated is True at 21/12/2018 14:21:51 DerivedClass01: LoggerInitiated is True at 21/12/2018 14:21:51 BaseClass: LoggerInitiated is True at 21/12/2018 14:21:52 DerivedClass02: LoggerInitiated is True at 21/12/2018 14:21:52 */ (这很明显-我应该早点意识到...)


从包含静态Logger的基类继承

类似于上面,但是有一个静态实例,而不是实例Logger。在实例化Logger之前创建它,并且不要在DerivedClasses构造函数中创建新实例。

代码:

BaseClass

输出:

class Program
{
    static void Main( string[] args )
    {

        BaseClass.LoggerInstantiated = true;
        BaseClass.InstantiatedAt = DateTime.Now;

        DerivedClass01 dc01 = new DerivedClass01();

        // added to create delay between the instantiation of different derived classes
        System.Threading.Thread.Sleep( 1000 );

        DerivedClass02 dc02 = new DerivedClass02();
    }
}

class BaseClass : ConfigurationSection
{
    // These variables are used for illustrative purposed,
    // instead of showing my `Logger' class
    public static bool LoggerInstantiated;
    public static DateTime InstantiatedAt;

    public BaseClass()
    {
        Console.WriteLine(string.Format("BaseClass:\n\r\tLoggerInitiated is {0} at {1}\n\r", LoggerInstantiated, InstantiatedAt));
    }
}

class DerivedClass01 : BaseClass
{
    public DerivedClass01()
    {
        Console.WriteLine( string.Format( "DerivedClass01:\n\r\tLoggerInitiated is {0} at {1}\n\r", LoggerInstantiated, InstantiatedAt ) );
    }
}

class DerivedClass02 : BaseClass
{
    public DerivedClass02()
    {
        Console.WriteLine( string.Format( "DerivedClass02:\n\r\tLoggerInitiated is {0} at {1}\n\r", LoggerInstantiated, InstantiatedAt ) );
    }
}

结论:

这似乎是一个合理的选择。从/* BaseClass: LoggerInitiated is True at 21/12/2018 14:05:56 DerivedClass01: LoggerInitiated is True at 21/12/2018 14:05:56 BaseClass: LoggerInitiated is True at 21/12/2018 14:05:56 DerivedClass02: LoggerInitiated is True at 21/12/2018 14:05:56 */ 派生的每个类都可以访问相同的BaseClass,而不必将其作为参数传递。

3 个答案:

答案 0 :(得分:2)

您不必将记录器定义为单例或静态类。这会导致无法测试代码的问题。还有其他方法可以确保所有类都使用相同的实例。

我将从界面开始。编写它,以便它描述您希望类如何使用它,就像这样:

public interface ILogger
{
    void Log(string message);
}

然后,将其注入到您的类中,如下所示:

public class YourClass
{
    private readonly ILogger _logger;

    public YourClass(ILogger logger)
    {
        _logger = logger;
    }

    private void MethodThatDoesWhatever()
    {
        try
        {
            // do something
        }
        catch(Exception ex)
        {
            _logger.Log(ex.ToString());
        }
    }
}

您尚未编写ILogger的实现,但这就是重点。如果您的类即使没有实现也可以使用ILogger,则意味着您的类与ILogger的实现完全脱钩。它取决于抽象,即依赖反转。

如何确保所有类都使用同一实例?如果您使用的是温莎之类的依赖项注入容器,则可能看起来像这样:

var container = new WindsorContainer();
container.Register(
    Component.For<ILogger, YourLoggerImplementation>(),
    Component.For<YourClass>(),
    Component.For<SomeOtherClassThatNeedsLogger>()
);

当您向容器请求YourClass的实例时,

var myClass = container.Resolve<YourClass>();
  • 它将尝试创建YourClass的实例。
  • 它将确定构造函数是否需要ILogger
  • 您告诉它ILogger使用的组件(类)是YourLoggerImplementation,因此它将创建其中一个组件并将其传递给构造函数。
  • 除非另行说明,否则它将不会创建YourLoggerImplementation的新实例。它只会保留相同的实例并重用它,因此现在您所有的类都使用相同的记录器实例。

关于如何使用IoC容器,我已经完全蒙蔽了很多(Windsor,AutoFac,Unity,Ninject,.NET ServiceCollection,等等。)值得理解,但这超出了此答案的范围。 。要了解更多信息,您需要从所编写的应用程序类型开始。例如,您将搜索“在ASP.NET MVC中使用依赖项注入”或“在WPF中使用依赖项注入”,因为开始的方式与应用程序的种类有关。 (即使那样我还是过分简化了,但是IMO最好以这种方式开始。当您看到一个针对您正在使用的应用程序类型量身定制的示例时,它更有可能被点击。)

然后,您必须编写记录器实现本身。如果它正在写入文件,并且多个类实例都在使用该文件,则建议使用将消息放入ConcurrentQueue中的实现。然后,当队列达到一定大小或以固定的计时器间隔(或两者都有)时,将消息刷新到文件中。使用ConcurrentQueue时,多个线程可以同时添加消息,而一个线程可以将消息从队列中取出,并在其他线程写入新消息的同时将它们写入文件。

答案 1 :(得分:0)

我同意@rashmatash。我自己有一个记录器,它是静态的,可以在任何地方使用。它可以创建新文件,也可以追加到现有文件中。

public方法接受并例外,可能是自定义消息,还可能是所需日志文件的完整路径的字符串。

public static void LogError(Exception Ex, string Message = null, string LogFile = null)

如果LogFile保留为空,则将在应用程序基本目录+ \ Logs中创建/附加一个名为ErrorLog.txt的日志文件。

这是主要部分:

if (LogFile == null || LogFile == String.Empty)
            {
                LogFile = AppDomain.CurrentDomain.BaseDirectory + @"\Logs\ErrorLog.txt";
            }

            string logDir = Path.GetDirectoryName(LogFile);
            int lineNo = GetExceptionLineNumber(Ex);
            string message = CreateMessageString(Ex, lineNo, Message);

            try
            {
                if (VerifyOrCreateDirectory(logDir) == true)
                {
                    StreamWriter streamWriter = new StreamWriter(path: LogFile, append: true);

                    string logEntryHeaderInfo = DateTime.Now + " :: " + AppDomain.CurrentDomain.FriendlyName.ToString() + " :: " + Environment.UserName + " :: " + Environment.MachineName;
                    streamWriter.WriteLine(logEntryHeaderInfo + " :: " + message);
                    streamWriter.Flush();
                    streamWriter.Close();
                }
            }

答案 2 :(得分:0)

您可以使用单例模式

Singleton Pattern C#

collectstatic

因此,您可以在任何部分或代码中使用同一实例:

class Attribute:
    def __init__(self, pos_x, pos_y):
        self.attr_top = Toplevel()
        print(root.winfo_x() + pos_x)
        self.attr_top.geometry('+' + str(root.winfo_x() + pos_x) + '+' + str(root.winfo_y() + pos_y))
        self.name_var = StringVar()
        self.name_var.set('name')
        self.name_label = Label(self.attr_top, textvariable = self.name_var)
        self.name_label.grid(row = 0, column = 0)
        self.name_enter = Entry(self.attr_top)
        self.name_enter.grid(row = 0, column = 1)
        self.next_name_var = StringVar()
        self.next_name_var.set('link')
        self.next_name_label = Label(self.attr_top, textvariable = self.next_name_var)
        self.next_name_label.grid(row = 1, column = 0)
        self.next_name_enter = Entry(self.attr_top)

        self.finish_button = Button(self.attr_top, text = 'finsh edit', command = self.button_close_top)
        self.finish_button.grid(row = 3, column = 0, columnspan = 2)
        self.attr_top.bind('<Return>', self.return_close_top)
        self.next_name_enter.grid(row = 1, column = 1)

    def button_close_top(self):
        self.name = self.name_enter.get()
        self.next_name = self.next_name_enter.get()
        self.attr_top.destroy()

    def return_close_top(self, event):
        self.button_close_top()

    def save_attr(self, event):
        name = self.attr.name
        next_name = self.attr.next_name

        print('jinru')
        name_font = tkfont.Font(size = 10)
        self.enter_text.insert(END, name)
        self.enter_text.tag_add(name, 1.0, END)
        self.enter_text.tag_config(name, font = name_font, offset = 2, underline = 1)

    def edit_brief(self):
        print('edit brief')
        self.attr = Attribute(self.widget_pos_x, self.widget_pos_y)
        self.enter_text = Text(self.window_frame, width = 30, height = 3)
        self.enter_text.place(x = self.widget_pos_x, y = self.widget_pos_y)
        self.attr.attr_top.bind('<Destroy>', self.save_attr)

或者也许您想使用像Ninject,Unity,Simple Injector这样的依赖注入框架