1

好的,首先我将描述我的目标,然后是我编写的代码,最后是问题

目标

拥有一个管理多个合约的通用类,并且能够在进行操作的那一刻找出它是在线还是离线情况。有一种非常简单的方法:为每个 Online-Offline 对创建一个类,该类实现合约并检查每个方法是否在线,并做出正确的调用。这正是我想要避免的。

仅供参考,在幕后它将是一个连接到WCF 服务的在线场景和一个连接到客户端本地数据库的离线场景

仅供参考 2:我试图避免拦截和 AOP 的东西,但我发现了一个死胡同。您可以看到这篇文章,我在其中实现了似乎是一个很好的解决方案,但是如果它在构造函数上已连接或未连接则稳定,但现实世界的场景需要在操作级别而不是构造函数级别进行此检查。

代码

它已准备好运行和测试:只需复制/粘贴到新的控制台应用程序上。

using System;
using System.Reflection;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;

namespace ConsoleApplication1
{
    public class Unity
    {
        public static IUnityContainer Container;

        public static void Initialize()
        {
            Container = new UnityContainer();

            Container.AddNewExtension<Interception>();
            Container.RegisterType<ILogger, OnlineLogger>();
            Container.Configure<Interception>().SetInterceptorFor<ILogger>(new InterfaceInterceptor());
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Unity.Initialize();

            var r = new Router<ILogger, OnlineLogger, OfflineLogger>();

            try
            {
                r.Logger.Write("Method executed.");
            }
            catch (CantLogException ex)
            {
                r.ManageCantLogException(ex);
            }

            Console.ReadKey();
        }
    }

    public class Router<TContract, TOnline, TOffline>
        where TOnline : TContract, new()
        where TOffline : TContract, new()
    {
        public TContract Logger;

        public Router()
        {
            Logger = Unity.Container.Resolve<TContract>();
        }

        public void ManageCantLogException(CantLogException ex)
        {
            // Is this an ugly trick? I mean, the type was already registered with online.
            Unity.Container.RegisterType<TContract, TOffline>();
            Logger = Unity.Container.Resolve<TContract>();

            var method = ((MethodBase)ex.MethodBase);
            method.Invoke(Logger, ex.ParameterCollection);
        }
    }

    public interface ILogger
    {
        [Test]
        void Write(string message);
    }

    public class OnlineLogger : ILogger
    {
        public static bool IsOnline()
        {
            // A routine that check connectivity
            return false;
            // Should I perform a "lock" here to make this tread safe?
        }

        public void Write(string message)
        {
            Console.WriteLine("Logger: " + message);
        }
    }

    public class OfflineLogger : ILogger
    {
        public void Write(string message)
        {
            Console.WriteLine("Logger: " + message);
        }
    }

    [System.Diagnostics.DebuggerStepThroughAttribute()]
    public class TestAttribute : HandlerAttribute
    {
        public override ICallHandler CreateHandler(IUnityContainer container)
        {
            return new TestHandler();
        }
    }

    public class TestHandler : ICallHandler
    {
        public int Order { get; set; }
        public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
        {
            Console.WriteLine("It's been intercepted.");

            if (!OnlineLogger.IsOnline() && input.Target is OnlineLogger)
            {
                Console.WriteLine("It's been canceled.");
                throw new CantLogException(input.MethodBase, input.Inputs);
            }
            return getNext()(input, getNext);
        }
    }

    public class CantLogException : Exception
    {
        public MethodBase MethodBase { get; set; }

        public object[] ParameterCollection { get; set; }

        public CantLogException(string message)
            : base(message)
        {
        }

        public CantLogException(MethodBase methodBase, IParameterCollection parameterCollection)
        {
            this.MethodBase = methodBase;

            var parameters = new object[parameterCollection.Count];

            int i = 0;
            foreach (var parameter in parameterCollection)
            {
                parameters[i] = parameter;
                i++;
            }

            this.ParameterCollection = parameters;
        }
    }
}

问题

  1. 我提出的这个设计是线程安全的吗?

  2. 这是性能的耻辱吗?处理在线-离线情况的异常和那里的反思让我觉得我做错了。

  3. 我认为这是一个常见的要求,是不是有任何 api/fwk/whatever 可以进行在线-离线管理?感觉就像我在这里重新发明了轮船。

  4. 这是一个长期的问题:我真的不希望客户端(本例中的Program类)知道异常,是否有任何其他方法可以取消方法执行,而是通过拦截器上的异常?也欢迎任何其他方法。


我对付费的第三方东西不感兴趣,所以遗憾的是像PostSharp这样的东西不是我的选择。

4

3 回答 3

2

关于线程安全 - 特别是在您的评论建议锁定的情况下:

    public static bool IsOnline()
    {
        // A routine that check connectivity
        return false;
        // Should I perform a "lock" here to make this tread safe?
    }

您不能“将自己锁定在线”,尽管您想知道状态是否可以在检查语句和返回之间更改(请参见经典单例模式)是正确的,但这里担心的是无论您的检查是否是线程,连接都会断开安全与否。

在这种情况下,您只能处理(或选择不处理)抛出的异常

于 2012-09-14T16:18:24.520 回答
1

好的,最后,我有解决方案。我会分享它,因为我发现这在这种在线 - 离线场景中非常有用。有一点改进会很不错:这样一来,在线总是首先被击中,最好设置谁必须首先使用。但那是为了发布,这对于测试版来说就很好了。

using System;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;

namespace ConsoleApplication1
{
    public enum ConnectionStatus
    {
        Online,
        Offline,
        System // System checks connectivity
    }

    public static class Connectivity
    {
        private static ConnectionStatus ConnectionStatus = ConnectionStatus.Offline;

        public static void ForceConnectionStatus(ConnectionStatus connectionStatus)
        {
            ConnectionStatus = connectionStatus;
        }

        public static bool IsConnected()
        {
            switch (ConnectionStatus)
            {
                case ConnectionStatus.Online:
                    return true;
                case ConnectionStatus.Offline:
                    return false;
                case ConnectionStatus.System:
                    return CheckConnection();
            }
            return false;
        }

        private static bool CheckConnection()
        {
            return true;
        }
    }

    public class Unity
    {
        public static IUnityContainer Container;

        public static void Initialize()
        {
            Container = new UnityContainer();

            Container.AddNewExtension<Interception>();
            Container.RegisterType<ILogger, OnlineLogger>();
            Container.Configure<Interception>().SetInterceptorFor<ILogger>(new InterfaceInterceptor());
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Unity.Initialize();

            var r = new Router<ILogger, OnlineLogger, OnlineLogger>();

            Connectivity.ForceConnectionStatus(ConnectionStatus.Offline);

            Console.WriteLine("Calling Online, will attend offline: ");

            r.Logger.Write("Used offline.");

            Connectivity.ForceConnectionStatus(ConnectionStatus.Online);

            Console.WriteLine("Calling Online, will attend online: ");

            r.Logger.Write("Used Online. Clap Clap Clap.");

            Console.ReadKey();
        }
    }

    public class Router<TContract, TOnline, TOffline>
        where TOnline : TContract
        where TOffline : TContract
    {
        public TContract Logger;

        public Router()
        {
            Logger = Unity.Container.Resolve<TContract>();
        }
    }

    public interface IOnline
    {
        IOffline Offline { get; set; }
    }

    public interface IOffline
    {
    }

    public interface ILogger
    {
        [Test()]
        void Write(string message);
    }

    public class OnlineLogger : ILogger, IOnline
    {
        public IOffline Offline { get; set; }

        public OnlineLogger()
        {
            this.Offline = new OfflineLogger();
        }

        public void Write(string message)
        {
            Console.WriteLine("Online Logger: " + message);
        }
    }

    public class OfflineLogger : ILogger, IOffline
    {
        public IOnline Online { get; set; }

        public void Write(string message)
        {
            Console.WriteLine("Offline Logger: " + message);
        }
    }

    [System.Diagnostics.DebuggerStepThroughAttribute()]
    public class TestAttribute : HandlerAttribute
    {
        public override ICallHandler CreateHandler(IUnityContainer container)
        {
            return new TestHandler();
        }
    }

    public class TestHandler : ICallHandler
    {
        public int Order { get; set; }

        public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
        {
            Console.WriteLine("It's been intercepted.");

            if (!Connectivity.IsConnected() && input.Target is IOnline)
            {
                Console.WriteLine("It's been canceled.");

                var offline = ((input.Target as IOnline).Offline);

                if (offline == null)
                    throw new Exception("Online class did not initialized Offline Dispatcher.");

                var offlineResult = input.MethodBase.Invoke(offline, this.GetObjects(input.Inputs));

                return input.CreateMethodReturn(offlineResult, this.GetObjects(input.Inputs));
            }

            return getNext()(input, getNext);
        }

        private object[] GetObjects(IParameterCollection parameterCollection)
        {
            var parameters = new object[parameterCollection.Count];

            int i = 0;
            foreach (var parameter in parameterCollection)
            {
                parameters[i] = parameter;
                i++;
            }
            return parameters;
        }
    }
}
于 2012-09-14T21:45:40.230 回答
0

我想提出另一种可能的方法。它与您的计划完全不同,但可能会更好。

基本思想是将您的应用程序分成两部分。第一部分是您所描述的 - 用户界面。但是,它是假设您处于离线状态的情况下编写的。它将所有内容存储到本地数据库/文件/内存存储中。

然后,您拥有程序的另一部分,可能在另一个负责同步的线程(或可能在另一个进程中)上。它查看数据存储,等待更改,如果在线,它将它们写入服务器。当更新从服务器下来时,数据存储将需要引发事件来告诉主程序发生了一些变化,但无论如何它都必须这样做。如果您处于离线状态,同步器不会执行任何操作,但所有数据更改都会自动安全地隐藏起来,直到您稍后重新在线。

这种方法的好处是您无需担心任何地方的在线/离线切换,除了专门处理同步的代码部分。假设您处于离线状态,您已经设置了默认情况。您可以完全摆脱所有拦截/ AOP 的东西。

于 2012-09-17T04:45:35.663 回答