zoukankan      html  css  js  c++  java
  • 从抽象谈起(一):工厂模式与策略模式

    抽象的意思是,抽取不同事物的共性而成的一种新事物。为什么用事物一词?因为抽象未必抽的是物,也可能是事。
    抽象是编程的重要思想之一,其主要目的是为了减少代码重复,使其更易维护。
    抽象就是让变化的事物得到一致的处理方式。

    抽象是如何应用的?我们怎么去抽象?

    当我们面临有共同特性的事物时,需要对它们统一处理,那么就需要抽象。而这种共性的事物在实际项目中会经常碰到。而且在我们使用的各种框架中应用广泛。比如说,用户打开不同的网页,都需要去展现页面,那么所有的网页都有一个共性就是展现,而不同的网页又具有不同的行为;所以在处理网页展现时,只需要处理网页们的抽象的东西——展现。这个“处理网页展现”的代码一般在框架内部实现。他对所有的网页处理都是调用抽象网页的展现代码,所以他的代码是一致不变的。再比如说我们点击某一些按钮,会触发各种事件,点击按钮的行为都是一致的,而事件的内容缺各不相同。那么在点击的这个行为上的处理也是一致的,就是触发事件的内容,至于事件内容的本身,那就是具体的实现问题,跟处理点击没有关系。我们把统一处理抽象事物的代码叫上层代码。抽象就是为了上层代码的一致性,不需要因为具体事物的改变而改变。

    抽象与模式

    也许大家都知道设计模式,这是经典的实际应用中碰到的各种常见问题而归纳出来的编程技巧,其中大多数都离不开抽象这一概念。掌握的抽象的思想,再去理解他们更容易些。

    工厂模式
    工厂模式是最易理解的模式之一,他是通过一个工厂类,创建抽象对象(其实是具体的实际对象),因为是抽象对象,所以其他代码在使用这些抽象对象的共性时只需要通过工厂类获取对象即可,而不需要具体new每个实际对象。
    代码示例:

    public interface IUserRepository
    {
    	IEnumerable<Users> GetUsers();
    }
    
    namespace DataRepository.MySql
    {
    	public class UserRepository : IUserRepository
    	{
    		private MySql _db;
    		public UserRepository()
    		{
    			_db = new MySql(ConnectionStringManager.Get("MySql"));
    		}
    		
    		public IEnumerable<Users> GetUsers()
    		{
    			return _db.ExecuteSql("sql").ToList<Users>();
    		}
    	}
    }
    
    namespace DataRepository.SqlServer
    {
    	public class UserRepository : IUserRepository
    	{
    		public IEnumerable<Users> GetUsers()
    		{
    			return GetDataContext(ConnectionStringManager.Get("SqlServer")).Users.AsEnumerable();
    		}
    	}
    }
    public class RepositoryFactory
    {
    	public IUserRepository GetUserRepository()
    	{
    		return CreateInsnace(AppSettings.Get("CurrentDatabase"),"UserRepository");
    	}
    }
    
    public class UserManager()
    {
    	public IEnumerable<Users> GetUsers()
    	{
    		return RepositoryFactory.GetUserRepository().GetUsers();
    	}
    }

    上面的代码看起来比较简单,继承自接口,反射实例化具体子类便可。但抽象意味着是具体实现,而不是继承,继承只是实现的一种。所以在继承上要慎用。而大多数的模式也都是采用各种组合。其不外乎就是抽象出共同的接口,组合接口的实现。

    策略模式
    策略模式的应用场景我们几乎都碰到过,比如现在都比较流行SinaWeibo登录和QQ登录,再加上自己的Email登录,每种登录方式都有不同的实现,因为Sina和QQ这种OAuth的登录都需要回调网页(其实就是用来验证用户有效性的),而我们的Email登录也需要验证,总不能写3个登录验证页面吧?当然,写三个也不是不可以,但是如果我们还是Wap站,那就是6个,如果再有其他的站点,那就不知道要写多少个了。至少页面数量会很多。如果用MVC框架的话,倒是可以用公共的Controller来省去,那至少也要三个Action,而这三个页面其实也有共性,必然存在代码重复。我们需要消除这种重复,万一哪天再来个开心、人人等登录实现,又会增加很多页面;所以要把因为变化牵扯出来的变化保持不变。

    可以看下面的实现代码,实现了3种登录方式的登录、验证和退出。

    //登录地址策略接口
    public interface ILogin
    {
    	public string GetLoginUrl(HttpContextBase context);
    }
    //第三方用户的验证策略接口
    public interface IAuthenticate
    {
    	public TrdUser Authenticate(HttpContextBase context);
    }
    //退出地址策略接口
    public interface ILogout
    {
    	public string GetLogoutUrl(HttpContextBase context);
    }
    //新浪Auth的策略实现
    public class SinaAuth : ILogin,IAuthenticate,ILogout
    {
    	public string GetLoginUrl(HttpContextBase context)
    	{
    		return "http://weibo.com/oauth/login";
    	}
    
    	public TrdUser Authenticate(HttpContextBase context)
    	{
    		return new TrdUser { Name = "我来自新浪" };
    	}
    	
    	public string GetLogoutUrl(HttpContextBase context)
    	{
    		return "http://weibo.com/oauth/logout";
    	}
    }
    //QQAuth的策略实现
    public class QQAuth : ILogin,IAuthenticate
    {
    	public string GetLoginUrl(HttpContextBase context)
    	{
    		return "http://qq.com/oauth/login";
    	}
    
    	public TrdUser Authenticate(HttpContextBase context)
    	{
    		return new TrdUser { Name = "我来自QQ" };
    	}
    }
    //本网站默认策略的实现
    public class DefaultAuth :ILogin, IAuthenticate,ILogout
    {
    	public string GetLoginUrl(HttpContextBase context)
    	{
    		return "http://mysite.com/oauth/login";
    	}	
    	
    	public TrdUser Authenticate(HttpContextBase context)
    	{
    		return new TrdUser { Name = "我来自Email" };
    	}
    	
    	public string GetLogoutUrl(HttpContextBase context)
    	{
    		return "http://mysite.com/oauth/logout";
    	}
    }
    //策略组装类
    public class Login
    {
    	private HttpContextBase _context;
    	public Login(HttpContextBase context)
    	{
    		_context = context;
    	}
    	//获取登录地址
    	public string GetLoginUrl(ILogin login)
    	{
    		return login.GetLoginUrl(_context);
    	}
    	//获取退出地址
    	public string GetLogoutUrl(ILogout logout)
    	{
    		return logout.GetLogoutUrl(_context);
    	}
    	//获取本站用户信息
    	public User Authenticate(IAuthenticate auth)
    	{
    		var trdUser = auth.Authenticate(_context);
    		return xx.GetUser(trdUser);
    	}
    }
    //策略工厂,根据不同的type创建不同的策略
    public class AuthFactory
    {
    	private string _authType;
    	public AuthFactory(string authType)
    	{
    		_authType = authType;
    	}
    
    	public ILogin GetLogin()
    	{
    		return (ILogin)CreateInstance(typeof(ILogin));
    	}
    
    	public IAuthenticate GetAuth()
    	{
    		return (IAuthenticate)CreateInstance(typeof(IAuthenticate));
    	}
    	
    	public ILogout GetLogoutUrl()
    	{
    		return (ILogout)CreateInstance(typeof(ILogout));
    	}
    	
    	private object CreateInstance(Type type)
    	{
    		//通过反射获取具体的类型
    		var instance = ....;
    		//若是没有实现,就使用默认的。
    		if(instance == null) return new DefaultAuth();
    	}
    }
    
    //本站的页面具体代码
    public AccountController : Controller 
    {
    	private Login GetLogin()
    	{
    		return new Login(HttpContext);
    	}
    
    	//登录地址
    	public ActionResult Login(string authType)
    	{
    		var factory = new AuthFactory(authType);
    		return Redirect(GetLogin().GetLoginUrl(factory.GetLogin()));
    	}
    	
    	//验证地址,不管是Email还是第三方登录回调,均使用该地址。
    	public ActionResult Authenticate(string authType)
    	{
    		var factory = new AuthFactory(authType);
    		var user = GetLogin().Authenticate(factory.GetAuth());
    		//....
    	}
    	
    	//退出地址
    	public ActionResult Logout(string authType)
    	{
    		var factory = new AuthFactory(authType);
    		
    		return Redirect(GetLogin().GetLogoutUrl(factory.GetLogin()));
    	}
    }


    因为我们在获取具体策略的时候依然要判断该使用哪种策略,所以用工厂模式来创建具体的策略。但是仍然能看到Controller代码的丑陋之处,这就需要AOP的实现来避免重复的代码,后面我们会讲到。

  • 相关阅读:
    【总结】st表
    【luogu】p2024 食物链
    【总结】stl(以后还会慢慢补上
    【总结】二叉堆
    【luogu】p1631 序列合并
    才子们博客地址
    Lemon测评软件使用说明 (对比cena)
    Cena编译器的使用 及任大佬和禚大佬解释(O2优化、C++11特性、开栈)值得大家学习
    编程求100内的素数
    【关于德育和道德方面】
  • 原文地址:https://www.cnblogs.com/snake-hand/p/3161575.html
Copyright © 2011-2022 走看看