
编程语言,框架相关专家
一、FileResult 1、简介 表示一个用于将二进制文件内容发送到响应的基类。它有三个子类: FileContentResultFilePathResultFileStreamResult 推荐阅读:https://www.cnblogs.com/weiweixiang/p/5667355.html 2、FilePathResult 首先、创建一个mvc5项目、然后添加一个FileTest控制器,添加以下方法 public ActionResult Export() { // Response.ContentType指定文件类型 可以为application/ms-excel || application/ms-word || application/ms-txt || application/ms-html return File(Server.MapPath("/UserData/test.docx"), "application/ms-word", "test.docx"); } <p> <a href='/filetest/export' download>下载</a> </p> 使用非常方便,这样即可实现下载 3、FileContentResult public ActionResult Getbg() { string bgimg = AppDomain.CurrentDomain.BaseDirectory + "/UserData/bg.jpg"; Image img = Image.FromFile(bgimg); byte[] bytes = ImageToBytes(img); return File(bytes, "image/jpeg"); } <img src="/filetest/Getbg" width="300" alt="" /> 使用非常方便,这样即可实现图片的显示,在临时描绘图片并展示的场景中非常实用。 4、FileStreamResult public ActionResult ExportDoc() { var path = Server.MapPath("/UserData/test.docx"); var fileName = HttpUtility.UrlEncode("test.docx", Encoding.GetEncoding("UTF-8")); return File(new FileStream(path, FileMode.Open), "application/ms-word", fileName); } <a href='/filetest/exportdoc' download>使用FileStreamResult下载Doc</a> 二、JS请求二进制文件 <p> <button onclick="showBg()">JS显示图片</button> <div id="divImg"> </div> </p> <script type="text/javascript"> //window.location.href = "Export"; var showBg = function () { var xmlhttp = new XMLHttpRequest(); xmlhttp.open("GET", "/filetest/Getbg", true); xmlhttp.responseType = "blob"; xmlhttp.setRequestHeader("client_type", "DESKTOP_WEB"); xmlhttp.onload = function () { if (this.status === 200) { var blob = this.response; var img = document.createElement("img"); img.onload = function (e) { window.URL.revokeObjectURL(img.src); }; img.src = window.URL.createObjectURL(blob); img.width = 500; $("#divImg").html(img); } }; xmlhttp.send(); }; </script> jquery并不支持流文件, js重要实现来自于情郎的博文:ajax 请求二进制流 图片 文件 XMLHttpRequest 请求并处理二进制流数据 之最佳实践 知识点: Content-Disposition http://www.jb51.net/article/30565.htm header中Content-Disposition的作用与使用方法: 当代码里面使用Content-Disposition来确保浏览器弹出下载对话框的时候。 response.addHeader("Content-Disposition","attachment");一定要确保没有做过关于禁止浏览器缓存的操作。如下: response.setHeader("Pragma", "No-cache"); response.setHeader("Cache-Control", "No-cache"); response.setDateHeader("Expires", 0); http://blog.csdn.net/iamiwangbo/article/details/52911716 XMLHttpRequest 使用blob实现文件的下载和上传 本文源码下载:https://gitee.com/zmsofts/XinCunShanNianDaiMa/blob/master/ActionResultOfMvc5Study.rar 参考: https://msdn.microsoft.com/zh-cn/library/system.web.mvc.fileresult.aspx http://www.cnblogs.com/bmib/p/3518486.html http://www.runoob.com/ajax/ajax-intro.html ajax学习 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest http://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html 讲解XMLHttpRequest 1 和2 的区别 http://www.cnblogs.com/cdemo/p/5225848.html https://www.cnblogs.com/weiweixiang/p/5667355.html C# byte数组与Image的相互转换 https://www.cnblogs.com/xielong/p/5940535.html
由于公司的项目才接触到ABP这个框架,当时就觉得高大上,什么IOC、AOP、ddd各种专业词汇让人激情 澎湃,但在使用过程中碰到了许多坑,可能也许是没有去看源码导致的,但工作确实没有那么多时间让人去慢慢研究。很久之前想手动搭建这个框架了,但是各种理由,你懂的。但是要在技术上得到大的提升就得静的下心去研究,学到大神的思想和精髓,运用到实际中去,才能去体验更开阔的天地。 本文以创建博客为思路,一步步构建整个项目,在摸索中进步,也希望能够帮助到有需要的人。 一、基础架构 第一部分主要是搭建好整个项目的骨架,连通各个项目 创建MVC5项目(ZmBlog.Web),手动引入Abp、Abp.Web、Abp.Web.Mvc、Abp.Web.Api 详情请看上一篇第一部分:http://www.cnblogs.com/xcsn/p/7941541.html 接下来,继续创建其他类库ZmBlog.Core、ZmBlog.Infrastructure、ZmBlog.Application。 引入相关nuget包,所有项目引用abp,ZmBlog.Infrastructure引用Abp.EntityFramework,web项目引用后两个 Install-Package Abp -Version 0.8.4 Install-Package Abp.EntityFramework -Version 0.8.4 Install-Package Abp.Web.Mvc -Version 0.8.4 Install-Package Abp.Web.Api -Version 0.8.4 二、ZmBlog.Core ZmBlog.Core是DDD的核心,实体、领域服务、事件等一般都写在这里,同时也定义了仓储的接口,但实现放在基础设施层。 首先,添加类ZmBlogCoreModule,如下 namespace ZmBlog.Core { public class ZmBlogCoreModule:AbpModule { public override void Initialize() { IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly()); } } } ZmBlogCoreModule必须依赖于AbpModule,ZmBlogCoreModule是自定义模块第一个启动的,另外,ZmBlogCoreModule启动之前,abp会先启用内部的AbpKernelModule。 AbpKernelModule类是Abp框架自己的Module,它也跟所有其他的Module一样继承自AbpModule,重写PreInitialize,Initialize,PostInitialize三个主要成员。 更详细的请参考:https://www.cnblogs.com/Azula/archive/2015/11/23/4989157.html 现在,定义一个实体文章分类Category,继承自Entity<TPrimaryKey> public class Category: Entity<string> { public Category() { Id = Guid.NewGuid().ToString(); } /// <summary> /// 类别名称 /// </summary> public string Name { get; set; } /// <summary> /// 状态(0隐藏1显示) /// </summary> public int Status { get; set; } } 以下是Entity<TPrimaryKey>的成员,其中定义了主键Id(可重写) 接着,添加一个仓储接口ICategoryRepository public interface ICategoryRepository : IRepository<Category,string>{} 另外,还可以添加领域服务ICategoryDomainService、CategoryDomainService,对于业务简单的模块,可以去掉领域服务,直接在应用层处理。 public interface ICategoryDomainService:IDomainService{} public class CategoryDomainService : DomainService, ICategoryDomainService{} 三、ZmBlog.Infrastructure 1.定义模块 创建一个模块ZmBlogDataModule,依赖模块AbpEntityFrameworkModule会自动注册了所有仓储接口 [DependsOn(typeof(ZmBlogCoreModule),typeof(AbpEntityFrameworkModule))] public class ZmBlogDataModule : AbpModule { public override void Initialize() { IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly()); } } 2.使用EF作为orm 使用以下命令引入包 Install-Package EntityFramework -Version 6.1.3 Install-Package Castle.Windsor -Version 3.3.0 (1)创建ZmBlogDbContext public class ZmBlogDbContext: AbpDbContext { public ZmBlogDbContext() : base("DefaultConnection") { } public ZmBlogDbContext(string nameOrConnectionString) : base(nameOrConnectionString) { } //This constructor is used in tests public ZmBlogDbContext(DbConnection connection) : base(connection, true) { } public DbSet<Category> Categorys { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); modelBuilder.Configurations.Add(new CategoryCfg());//使用独立配置 } } DefaultConnection是连接字符串名称,在web项目添加或修改如下 <connectionStrings> <add name="DefaultConnection" connectionString="Data Source=.;Initial Catalog=ZmBlogDb;Integrated Security=True" providerName="System.Data.SqlClient" /> </connectionStrings> CategoryCfg是对实体类型的配置,如设置Id为主键 public class CategoryCfg: EntityTypeConfiguration<Category> { public CategoryCfg() { HasKey(s => s.Id); } } (2)使用codefirst模式 1.启用迁移 启用:Enable-Migrations 自动生成配置文件:Configuration.cs 2.创建迁移 add-migration AddCategory 自动生成迁移文件:201712040256502_AddCategory 3.执行迁移 update-database -vebose 自动生成数据库ZmBlogDb、迁移记录表__MigrationHistory、表Categories (3)实现仓储 基础类BaseRepository,继承实现增删改查 public abstract class BaseRepository<TEntity, TPrimaryKey> :EfRepositoryBase<ZmBlogDbContext,TEntity,TPrimaryKey> where TEntity : class, IEntity<TPrimaryKey> { protected BaseRepository(IDbContextProvider<ZmBlogDbContext> dbContextProvider) : base(dbContextProvider) { } //add common methods for all repositories } public abstract class BaseRepository<TEntity> : BaseRepository<TEntity, string> where TEntity : class, IEntity<string> { protected BaseRepository(IDbContextProvider<ZmBlogDbContext> dbContextProvider) : base(dbContextProvider) { } //do not add any method here, add to the class above (since this inherits it) } 自定义的实现类,如下 public class CategoryRepository : BaseRepository<Category, string>, ICategoryRepository { public CategoryRepository(IDbContextProvider<ZmBlogDbContext> dbContextProvider) : base(dbContextProvider) { } } 四、ZmBlog.Application 1.添加模块 添加模块 ZmBlogApplicationModule namespace ZmBlog.Application { [DependsOn(typeof(ZmBlogCoreModule), typeof(ZmBlogDataModule))] public class ZmBlogApplicationModule:AbpModule { public override void Initialize() { IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly()); } } } 2.创建服务 在项目下添加文件夹Categories,然后添加服务接口和实现类,如 ICategoryApp public interface ICategoryApp:IApplicationService { string GetCategoryName(string id); } CategoryApp namespace ZmBlog.Application.Categories { public class CategoryApp : ApplicationService, ICategoryApp { private readonly IRepository<Category, string> _categoryRepository; public CategoryApp(IRepository<Category, string> categoryRepository) { _categoryRepository = categoryRepository; } //private readonly ICategoryRepository _categoryRepository; //public CategoryApp(ICategoryRepository categoryRepository) //{ // _categoryRepository = categoryRepository; //} public string GetCategoryName(string id) { //var categoryData = _categoryRepository.GetAll().FirstOrDefault(s=>s.Name == "硬件"); var category = new Category() { Name = "硬件" + DateTime.Now.ToString("yy-MM-dd HH:mm:ss"), Status = 1 }; _categoryRepository.Insert(category); CurrentUnitOfWork.SaveChanges(); return category.Name; } } } 五、ZmBlog.Web修改 1.增加依赖 ZmBlogWebModule是web的模块,上一篇文章创建的,增加依赖ZmBlogApplicationModule [DependsOn(typeof(AbpWebMvcModule),typeof(ZmBlogApplicationModule))] public class ZmBlogWebModule:AbpModule { //... } 2.修改控制器 public class HomeController : AbpController { private ICategoryApp _categoryApp; public HomeController(ICategoryApp categoryApp) { _categoryApp = categoryApp; } public ActionResult Index() { try { ViewBag.Name = _categoryApp.GetCategoryName(""); } catch (Exception e) { ViewBag.Name = "默认分类"; throw; } return View(); } //.....省略 3.页面修改 @{ ViewBag.Title = "Home Page"; } <div class="jumbotron"> <h1>I Like @ViewBag.Name</h1> </div> 去掉没用的,直接展示效果 六、展示 运行起来的效果 数据库 由于时间原因,一些细节没有详细讲,如有不清楚请留言 源码下载:https://github.com/jackchn/cnblogs-xcsn-source下的ZmBlogStudy.rar
一、简介 Castle是.net平台上的一个开源项目,为企业级开发和WEB应用程序开发提供完整的服务,用于提供IOC的解决方案.IOC被称为控制反转或者依赖注入(Dependency Injection)。 Windsor是Castle 的一个IOC容器。它构建于MicroKernel之上,功能非常之强大,能检测类并了解使用这些类时需要什么参数,检测类型和类型之间工作依赖性,并提供服务或者发生错误时提供预警的机制。 官网:http://www.castleproject.org 源码: https://github.com/castleproject/Core 源码: https://github.com/castleproject/Windsor 推荐教程:http://www.cnblogs.com/Terrylee/archive/2006/04/28/387503.html 二、实例 1.创建项目 在程序包管理器控制台引用Castle.Windsor Install-Package Castle.Windsor -Version 4.1.0 2.创建类库Business,并在web项目引用,新建接口IUserService和实现类UserService public interface IUserService { string Display(string mes); } public class UserService : IUserService { public string Display(string mes) { return "User say:" + mes; } } 3.添加安装类 安装的配置比较简单,无非是寻找安装类,并执行安装并获取容器,所有的安装类都需要继承自IWindsorInstaller,添加CustomMvcInstaller ,如下: namespace MvcWeb.CastleWindsor { public class CustomMvcInstaller : IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { container.Register( Component.For<IUserService>().ImplementedBy<UserService>() ); } } public class WindsorInit { private static WindsorContainer _container; public static WindsorContainer GetContainer() { if (_container == null) { _container = new WindsorContainer(); _container.Install( FromAssembly.This() ); } return _container; } public void CloseContex() { _container.Dispose(); } } } 4.修改home控制器 public class HomeController : Controller { private IUserService _userService; public HomeController() { WindsorContainer container = WindsorInit.GetContainer(); _userService = container.Resolve<IUserService>(new Arguments(new { })); } public ActionResult Index() { ViewBag.Name = _userService.Display("I am coming by Windsor"); return View(); } index.cshtml修改如下 <div class="jumbotron"> <h1>ASP.NET </h1> <h3>@ViewBag.Name </h3> ....... 大功造成,可以访问首页了 源码下载:https://gitee.com/zmsofts/XinCunShanNianDaiMa/blob/master/IocDemo.rar 参考文章: http://www.cnblogs.com/wuxilin/archive/2006/04/20/380475.html http://blog.csdn.net/eye_cng/article/details/44277151 http://blog.csdn.net/eye_cng/article/details/44277371 https://www.cnblogs.com/lanpingwang/p/6533738.html https://www.cnblogs.com/ceci/p/6877903.html
一、手工搭建平台 1.创建项目 创建MVC5项目,手动引入Abp、Abp.Web、Abp.Web.Mvc、Abp.Web.Api 使用nuget添加Newtonsoft.Json、Castle.Core、Castle.Windsor Install-Package Newtonsoft.Json -Version 8.0.3 Install-Package Castle.Windsor -Version 3.3.0 2.创建WebModule类 在App_Start下创建一个ZmBlogWebModule类型,DependsOn指示 ZmBlogWebModule依赖于AbpWebMvcModule,核心模块AbpWebMvcModule会在应用模块ZmBlogWebModule之前进行初始化,核心模块同时可以加载多个。 namespace ZmBlog.Web.App_Start { [DependsOn(typeof(AbpWebMvcModule))] public class ZmBlogWebModule:AbpModule { public override void PreInitialize() { //依赖注入注册之前,主要用于初始化默认的配置 //开启本地化语言、配置等Hangfire //关闭多租户、审计日志、AntiForgery等 } public override void Initialize() { //该方法通常是依赖注入注册的地方 IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly()); AreaRegistration.RegisterAllAreas(); //FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); } public override void PostInitialize() { //依赖注入注册之后调用,在这里可以安全地解析一个依赖 } public override void Shutdown() { //在应用关闭的时候调用 } } } 3.配置Global.asax MvcApplication 要继承AbpWebApplication,重写Application_Start来初始化abp的内部初始化 public class MvcApplication : AbpWebApplication { protected override void Application_Start(object sender, EventArgs e) { base.Application_Start(sender, e); } } 4.修改默认控制器 Home控制器必须集成于AbpController,否则会报错 好了,现在项目可以正常运行了 二、初始化过程 1.abp框架的启动是从Global.asax文件的Application_Start启动的,通过base去初始化。 2.AbpWebApplication的Application_Start方法中,通过AbpBootstrapper来将各个Abpmodule模块载入Abp框架中的AbpBootstrapper类在Abp.dll中, 其主要用于框架的基本配置的注册和初始化,AbpBootstrapper调用Initialize方法初始化 public abstract class AbpWebApplication : HttpApplication { protected virtual void Application_Start(object sender, EventArgs e) { ThreadCultureSanitizer.Sanitize(); //设置当前线程的区域性 AbpBootstrapper.IocManager.RegisterIfNot<IAssemblyFinder, WebAssemblyFinder>();//加载bin目录下的所有dll并注册 AbpBootstrapper.Initialize(); } } 3.关于AbpBootstrapper的Initialize()方法 public virtual void Initialize() { IocManager.IocContainer.Install(new AbpCoreInstaller());//注册系统框架级的所有配置类 IocManager.Resolve<AbpStartupConfiguration>().Initialize();//实例化配置类 _moduleManager = IocManager.Resolve<IAbpModuleManager>(); _moduleManager.InitializeModules(); } 4. IAbpModuleManager的实例调用其InitializeModules()初始化所有的Module public virtual void InitializeModules() { LoadAll(); var sortedModules = _modules.GetSortedModuleListByDependency(); sortedModules.ForEach(module => module.Instance.PreInitialize());//先完成所有Module的PreInitialize sortedModules.ForEach(module => module.Instance.Initialize());//再执行所有Module的Initialize sortedModules.ForEach(module => module.Instance.PostInitialize());//最后执行PostInitialize } 另外,AbpModule的基本信息是用AbpModuleInfo封装的,将一个abpmodule类封装成Type,Assembly以及模块的依赖模块的AbpModuleInfo等信息。 5.应用程序结束将在AbpWebApplication中调用AbpBootstrapper的Dispose方法,Dispose通过IAbpModuleManager,执行其ShutdownModules,关闭所有Module。 public virtual void ShutdownModules() { var sortedModules = _modules.GetSortedModuleListByDependency(); sortedModules.Reverse(); sortedModules.ForEach(sm => sm.Instance.Shutdown()); } 三、AbpBootstrapper 在核心启动类AbpBootstrapper中的两个至关重要的属性:IIocManager 和 IAbpModuleManager 。 IIocManager内部包装了一个Castle的依赖注入容器IWindsorContainer,所有类型的注册、解析、AOP机制的拦截器都是注册在该容器中的,将具体的注册还有解析功能分别包含在其父接口IIocRegistrar和IIocResolver中。 namespace Abp.Dependency { /// 此接口用于直接执行依赖项注入任务 public interface IIocManager : IIocRegistrar, IIocResolver, IDisposable { IWindsorContainer IocContainer { get; } /// 引用 Castle Windsor Container. new bool IsRegistered(Type type); /// 检测该类型是否已注册. new bool IsRegistered<T>(); /// 检测该类型是否已注册. } } 其中关系类图如下: 四、 AbpCoreInstaller AbpCoreInstaller只是完成注册系统框架级的所有配置类。Abp支持自动完成符合Conventional(基于约定)的组件的注册。 Conventional 的规则要通过继承IConventionalDependencyRegistrar接口实现。 如下图,ABP中继承自IConventionalDependencyRegistrar接口的四个类。 其中BasicConventionalRegistrar设置了所有继承至ITransientDependency,ISingletonDependency和IInterceptor接口的类都会被自动注册。 其他三个则分别注册AbpDbContext,ApiController和Controller的派生类。 BasicConventionalRegistrar的代码,其注册所有继承至ITransientDependency,ISingletonDependency和IInterceptor接口的类。 参考文章: https://www.cnblogs.com/farb/p/ABPModuleSystem.html http://www.cnblogs.com/xuzimian/p/5561708.html https://www.cnblogs.com/xuzimian/p/5579144.html http://www.cnblogs.com/skabyy/p/7295533.html http://www.cnblogs.com/1zhk/p/5277610.html https://www.cnblogs.com/huaizuo/p/4836853.html
本系列是基于aspnetboilerplate-0.8.4.0版本写的,其中原因是由于较高的版本太抽象难以理解和分析,对于还菜菜的我要花更多的时间去学习。 abp的源码分析学习主要来源于 HK Zhang ,他的博客是https://www.cnblogs.com/1zhk/ 一、什么是ABP ASP.NET Boilerplate(ABP)是现代新的Web应用程序的最佳实践和最流行的工具的起点。它的目标是实体模型、通用应用程序框架和项目模板。 ABP是一个建立在最新的ASP.NET的MVC和Web API技术的应用框架。它可以很容易地使用依赖注入、日志记录、验证、异常处理、本地化等,也使用流行的框架和库。 ABP实现N层架构(域、应用、基础设施和表示层)和领域驱动设计(实体库,仓储,领域/应用服务、数据传输对象..)。还实现并提供了一个良好的基础结构来实现诸如依赖注入的最佳实践。 ABP创建你的项目启动模板。它包括默认使用的大多数框架和库。你也可以选择单页(AngularJS)或多页(MVC)架构,EntityFramework或NHibernate作为ORM。 二、项目结构 ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序。 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module).其中13个依赖于Abp这个核心包,另外两个包(FluentMigration,Web.Resources)相对独立。 Abp.AutoMapper: 实现对象之间的自动映射。 Abp.EntityFramework:通过EntityFramework实现数据访问层。 Abp.FluentMigrator :对FluentMigrator库做了易用性扩展。 Abp.HangFire:集成了HangFire,用于实现后台工作任务。 Abp.MemoryDb:MemoryDB的数据访问层 Abp.MongoDB:MongoDB的数据访问层。这个模块是个鸡肋,实际项目中无法使用。原因后面会详述。 Abp.NHibernate:通过NHibernate实现数据访问层。 Abp.Owin:目前只有一个IAppBuilder的扩展方法,用来解决application启动时候的AppDomain CultureInfo Leak问题。 关于这个Issue可以点击这里。 Abp.RedisCache:Redis的数据访问层 Abp.Web: 提供给ASP.Net web application 使用的模块 Abp.Web.Api: 提供给ASP.Net web application Webapi使用的模块 Abp.Web.Mvc: 提供给ASP.Net web application MVC 使用的模块 Abp.Web.Api.Odata : 提供给ASP.Net web application Webapi Odata 使用的模块 Abp.Web.SignalR : 集成SignalR Abp.Web.Resources:这个模块中只有js和css代码,是用来扩展angularjs或jquery的? 三、文章目录 ABP架构学习系列二:ABP中配置的注册和初始化 ABP架构学习系列三:手工搭建ABP框架
原文:http://blog.csdn.net/fhzh520/article/details/46364603 VS代码生成工具ReSharper提供了丰富的快捷键,可以极大地提高你的开发效率。 配置单个键盘快捷键 1、在主菜单上,选择Tools | Options 2、在导航视图中选择Environment | Keyboard 3、在命令列表中,选择启动ReSharper的命令名称。浏览列表以查看或者编辑ReSharper的特定操作的键盘快捷方式。 在键盘快捷方式间切换或者恢复到当前选定方案的默认状态 1、在主菜单上,选择ReSharper | Options 2、选择Environment | Keyboard and Menus 3、在Keyboard and Menus选项卡上,选择 Visual Studio, ReSharper 2.x 或 IntelliJ IDEA或 None,点击Apply Scheme。如果你选择None,你可以在Visual Studio的本地选项菜单中定义任意一组自定义快捷键(Tools | Options | Environment | Keyboard) 。 完整的快捷键列表 代码分析(Code analysis) Command Shortcut(Visual Studio) Shortcut(IntelliJ IDEA/ReSharper 2.x) Toggle code analysis in the current file Ctrl+Shift+Alt+8 Ctrl+Shift+Alt+8 Go to next code issue (error, warning or suggestion) Alt+PgDn F12 Go to previous code issue (error, warning or suggestion) Alt+PgUp Shift+F12 Go to next error Shift+Alt+PgDn Alt+F12 Go to next error in solution Shift+Alt+PgDn Alt+F12 Go to previous error Shift+Alt+PgUp Shift+Alt+F12 Go to previous error in solution Shift+Alt+PgUp Shift+Alt+F12 Inspect this Ctrl+Shift+Alt+A Ctrl+Shift+Alt+A View type hierarchy(查看类型的层级) Ctrl+E,H Ctrl+Alt+H Inspection Results window Ctrl+Alt+V Ctrl+Alt+V 编码援助(Coding assistance) Command Shortcut(Visual Studio) Shortcut(IntelliJ IDEA/ReSharper 2.x) Show action list Ctrl+R,W Ctrl+R,W Paste multiple Ctrl+Shift+V Code cleanup Ctrl+E,C Ctrl+Alt+F Silent code cleanup Ctrl+E,F Ctrl+Shift+Alt+F Symbol code completion Ctrl+Space Ctrl+Space Smart code completion Ctrl+Alt+Space Ctrl+Shift+Space Import symbol completion Shift+Alt+Space Ctrl+Alt+Space Complete statement Ctrl+Shift+Enter Ctrl+Shift+Enter Parameter information Ctrl+Shift+Space Ctrl+P Quick documentation Ctrl+Shift+F1 Ctrl+Q Insert live template Ctrl+E,L Ctrl+J Surround with template Ctrl+E,U Ctrl+Alt+J Create file from template Ctrl+Alt+Insert Ctrl+Alt+Insert Generate code Alt+Insert Alt+Insert Move code up Ctrl+Shift+Alt+Up Ctrl+Shift+Alt+Up Move code down Ctrl+Shift+Alt+Down Ctrl+Shift+Alt+Down Move code left Ctrl+Shift+Alt+Left Ctrl+Shift+Alt+Left Move code right Ctrl+Shift+Alt+Right Ctrl+Shift+Alt+Right Extend selection Ctrl+Alt+Right Ctrl+W Shrink selection Ctrl+Alt+Left Ctrl+Shift+W Select containing declaration Ctrl+Shift+[ Ctrl+Shift+[ Duplicate a line or selection Ctrl+D Ctrl+D Comment with line comment Ctrl+Alt+/ Ctrl+/ Comment with block comment Ctrl+Shift+/ Ctrl+Shift+/ 导航和搜索(Navigation and search) Command Shortcut(Visual Studio) Shortcut(IntelliJ IDEA/ReSharper 2.x) Go to type Ctrl+T Ctrl+N 根据文件名定位 Ctrl+Shift+T Ctrl+Shift+N Go to file member Alt+\ Ctrl+F12 Go to symbol Shift+Alt+T Ctrl+Shift+Alt+N Navigate To Alt+` Ctrl+Shift+G Go to type of symbol Ctrl+Shift+F11 Ctrl+Shift+T 跳转到定义处 F12 Ctrl+B 跳转到实现类 Ctrl+F12 Ctrl+Shift+Alt+B Go to base symbols Alt+Home Ctrl+U Go to derived symbols Alt+End Ctrl+Alt+B Go to usage Shift+Alt+F12 Ctrl+Alt+F7 Go to containing declaration Ctrl+[ Ctrl+[ Go to next member/tag Alt+Down Alt+Down Go to previous member/tag Alt+Up Alt+Up Find usages Shift+F12 Alt+F7 Find usages (advanced) Ctrl+Shift+Alt+F12 Shift+Alt+F7 Highlight usages in file Shift+Alt+F11 Ctrl+Shift+F7 Go to previous usage Ctrl+Alt+PgUp Ctrl+Alt+Up Go to next usage Ctrl+Alt+PgDn Ctrl+Alt+Down Remove highlighting of usages Esc Esc View recent files Ctrl+, Ctrl+E View recent edits Ctrl+Shift+, Ctrl+Shift+Alt+Backspace Go to previous edit Ctrl+Shift+Backspace Ctrl+Shift+Backspace Go to related files Ctrl+Alt+F7 Ctrl+Shift+Alt+G View bookmarks Ctrl+` Ctrl+` Go to a numbered bookmark Ctrl+[numeric key] Ctrl+[numeric key] Set/remove a numbered bookmark Ctrl+Shift+[numeric key] Ctrl+Shift+[numeric key] Find Results window Ctrl+Alt+F12 Ctrl+Alt+U File structure Ctrl+Alt+F Ctrl+F11 To-do items Ctrl+Alt+D Stack Trace Explorer Ctrl+E,T Ctrl+Shift+E Locate in Solution Explorer Shift+Alt+L Shift+Alt+L Analyze references Shift+Alt+Y Shift+Alt+Y 重构(Refactorings) Command Shortcut(Visual Studio) Shortcut(IntelliJ IDEA/ReSharper 2.x) Refactor this Ctrl+Shift+R Ctrl+Shift+R Rename Ctrl+R,R F2 or Shift+F6 Move Ctrl+R,O F6 Safe delete Ctrl+R,D or Alt+Del Alt+Del Extract method Ctrl+R,M Ctrl+Alt+M Introduce variable Ctrl+R,V Ctrl+Alt+V Introduce field Ctrl+R,F Ctrl+Alt+D Introduce parameter Ctrl+R,P Ctrl+Alt+P Inline variable/method/field Ctrl+R,I Ctrl+Alt+N Encapsulate field Ctrl+R,E Ctrl+R,E Change signature Ctrl+R,S Ctrl+F6 单元测试(Unit testing) Command Shortcut(Visual Studio) Shortcut(IntelliJ IDEA/ReSharper 2.x) Run unit tests Ctrl+U,R Ctrl+T,R Debug unit tests Ctrl+U,D Ctrl+T,D Run all tests in solution Ctrl+U,L Ctrl+T,L Run current test session Ctrl+U,Y Ctrl+T,Y Repeat previous test run Ctrl+U,Y Ctrl+T,Y Append to test session Ctrl+U,A Ctrl+T,A Create new test session Ctrl+U,N Ctrl+T,N Unit Test Explorer Ctrl+Alt+U Ctrl+Alt+T Unit Test Sessions Ctrl+Alt+T Ctrl+Alt+R 工具窗口(Tool windows) Command Shortcut(Visual Studio) Shortcut(IntelliJ IDEA/ReSharper 2.x) File structure Ctrl+Alt+F Ctrl+F11 Find Results window Ctrl+Alt+F12 Ctrl+Alt+U Hierarchies window Ctrl+Alt+H Inspection Results window Ctrl+Alt+V Ctrl+Alt+V Stack Trace Explorer Ctrl+E,T Ctrl+Shift+E Templates Explorer window Alt+R,P Alt+R,P To-do items Ctrl+Alt+D Unit Test Explorer Ctrl+Alt+U Ctrl+Alt+T Unit Test Sessions Ctrl+Alt+T Ctrl+Alt+R Close recent tool Ctrl+Shift+F4 Ctrl+Shift+F4 Activate recent tool Ctrl+Alt+Backspace Ctrl+Alt+Backspace 相关推荐: https://www.cnblogs.com/ShaYeBlog/p/3554253.html https://www.cnblogs.com/luminji/p/3285505.html http://www.360doc.com/content/16/0411/19/7014874_549790915.shtml
原文:https://baike.baidu.com/item/冒泡排序/4602306?fr=aladdin 冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。 它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。 这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端,故名。 算法原理 冒泡排序算法的运作如下:(从后往前) 比较相邻的元素。如果第一个比第二个大,就交换他们两个。 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。 针对所有的元素重复以上的步骤,除了最后一个。 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较 算法分析 时间复杂度 若文件的初始状态是正序的,一趟扫描即可完成排序。所需的关键字比较次数 和记录移动次数 均达到最小值: , 。 所以,冒泡排序最好的时间复杂度为 。 若初始文件是反序的,需要进行 趟排序。每趟排序要进行 次关键字的比较(1≤i≤n-1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较和移动次数均达到最大值: 冒泡排序的最坏时间复杂度为 。 综上,因此冒泡排序总的平均时间复杂度为 。 算法稳定性 冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。 算法描述 c#实现 void Main(string[] args) { int temp = 0; int[] arr = {23, 44, 66, 76, 98, 11, 3, 9, 7}; Console.WriteLine("排序前的数组:"); foreach (int item in arr) { Console.Write(item + " "); } Console.WriteLine(); for (int i = 0; i < arr.Length - 1; i++) { for (int j = 0; j < arr.Length - 1 - i; j++) { Console.WriteLine("排序中的数组"+ i + "-" + j + ":"+ arr[j] +"-"+ arr[j + 1]); foreach (int item in arr) { Console.Write(item+" "); } Console.Write("\r\n\r\n"); if (arr[j] > arr[j + 1]) { temp = arr[j + 1]; arr[j + 1] = arr[j]; arr[j] = temp; } } } Console.WriteLine("排序后的数组:"); foreach (int item in arr) { Console.Write(item+" "); } } 输出结果 排序前的数组: 23 44 66 76 98 11 3 9 7 排序中的数组0-0:23-44 23 44 66 76 98 11 3 9 7 排序中的数组0-1:44-66 23 44 66 76 98 11 3 9 7 排序中的数组0-2:66-76 23 44 66 76 98 11 3 9 7 排序中的数组0-3:76-98 23 44 66 76 98 11 3 9 7 排序中的数组0-4:98-11 23 44 66 76 98 11 3 9 7 排序中的数组0-5:98-3 23 44 66 76 11 98 3 9 7 排序中的数组0-6:98-9 23 44 66 76 11 3 98 9 7 排序中的数组0-7:98-7 23 44 66 76 11 3 9 98 7 排序中的数组1-0:23-44 23 44 66 76 11 3 9 7 98 排序中的数组1-1:44-66 23 44 66 76 11 3 9 7 98 排序中的数组1-2:66-76 23 44 66 76 11 3 9 7 98 排序中的数组1-3:76-11 23 44 66 76 11 3 9 7 98 排序中的数组1-4:76-3 23 44 66 11 76 3 9 7 98 排序中的数组1-5:76-9 23 44 66 11 3 76 9 7 98 排序中的数组1-6:76-7 23 44 66 11 3 9 76 7 98 排序中的数组2-0:23-44 23 44 66 11 3 9 7 76 98 排序中的数组2-1:44-66 23 44 66 11 3 9 7 76 98 排序中的数组2-2:66-11 23 44 66 11 3 9 7 76 98 排序中的数组2-3:66-3 23 44 11 66 3 9 7 76 98 排序中的数组2-4:66-9 23 44 11 3 66 9 7 76 98 排序中的数组2-5:66-7 23 44 11 3 9 66 7 76 98 排序中的数组3-0:23-44 23 44 11 3 9 7 66 76 98 排序中的数组3-1:44-11 23 44 11 3 9 7 66 76 98 排序中的数组3-2:44-3 23 11 44 3 9 7 66 76 98 排序中的数组3-3:44-9 23 11 3 44 9 7 66 76 98 排序中的数组3-4:44-7 23 11 3 9 44 7 66 76 98 排序中的数组4-0:23-11 23 11 3 9 7 44 66 76 98 排序中的数组4-1:23-3 11 23 3 9 7 44 66 76 98 排序中的数组4-2:23-9 11 3 23 9 7 44 66 76 98 排序中的数组4-3:23-7 11 3 9 23 7 44 66 76 98 排序中的数组5-0:11-3 11 3 9 7 23 44 66 76 98 排序中的数组5-1:11-9 3 11 9 7 23 44 66 76 98 排序中的数组5-2:11-7 3 9 11 7 23 44 66 76 98 排序中的数组6-0:3-9 3 9 7 11 23 44 66 76 98 排序中的数组6-1:9-7 3 9 7 11 23 44 66 76 98 排序中的数组7-0:3-7 3 7 9 11 23 44 66 76 98 排序后的数组: 3 7 9 11 23 44 66 76 98
一、安装 新建一个没有身份验证的mvc项目 - SwaggerMvc5Demo,然后添加一个名为Remote(自定义)且包含基础读写(不想手写)的ApiController 开源地址:https://github.com/domaindrivendev/Swashbuckle 使用以下方法来添加 Swashbuckle: 从“程序包管理器控制台”窗口:Install-Package Swashbuckle -Version 5.6.0 从“管理 NuGet 程序包”对话框中: 右键单击“解决方案资源管理器” > “管理 NuGet 包”中的项目 将“包源”设置为“nuget.org” 在搜索框中输入“Swashbuckle” 从“浏览”选项卡中选择“Swashbuckle”包,然后单击“安装” 二、配置 1.项目属性->勾选生成xml文档文件 2.添加导航链接 在_Layout.cshtml文件中添加代码 <li>@Html.ActionLink("Swagger Help", "", "Swagger", new { area = "" }, null)</li> 3.修改SwaggerConfig.cs文件 安装完成后,在文件夹App_Start自动生成一个配置文件 swagger.config。这里可以做多版本控制,定义扩展功能,自定义显示ui的样式脚本,可以配置过滤、权限等。 例如: (1)为接口添加xml注释 string path = string.Format("{0}/bin/SwaggerMvc5Demo.XML", System.AppDomain.CurrentDomain.BaseDirectory); c.IncludeXmlComments(path); 使用前 使用后 (2)使用特性 [Obsolete] 在config中 开启 c.IgnoreObsoleteProperties(); 三、调用 1.直接调用 只要输入参数id,即可调用,非常方便 2.AutoRest AutoRest (https://github.com/Azure/AutoRest), 简单来说,就是一个EXE工具,可以根据Swagger的结构生成服务的客户端,这个客户端可以让你像调用本地方法一样调用服务,方法内部包装了Http请求。 详细教程请看 https://www.cnblogs.com/Leo_wl/p/5982882.html 扩展:ABP集成swagger http://www.cnblogs.com/wer-ltm/p/5776024.html 参考文章: http://www.cnblogs.com/oneapm/p/5390303.html http://blog.csdn.net/yuchen_0515/article/details/51762958
一、安装CentOs 以前在大学学过linux,但是对命令行总是有一种深深的排斥感,几年之后,还是又回来了。 1.下载 现在没法FQ,就算是FQ网速也是蜗牛一样慢,我使用阿里云的镜像站进行下载速度还是杠杠的。 我使用的镜像地址是 https://mirrors.aliyun.com/centos/7.4.1708/isos/x86_64/CentOS-7-x86_64-DVD-1708.iso ,可以根据自己需要去下载,总目录地址:https://mirrors.aliyun.com/centos/ 2.安装 安装教程网上非常多,这里也不详细解说,我是根据百度经验的文章搭建的,教程地址是https://jingyan.baidu.com/article/eae0782787b4c01fec548535.html 二、部署.NET core 1.发布程序 首先,创建.NET CORE 2 MVC项目,确保能运行成功;然后,发布.NET程序。 .net core 分为两种应用类型,分别是Portable application(便携应用)和Self-contained application(自宿主应用); 需要详细了解的请看 拥抱.NET Core,如何开发跨平台的应用并部署至Ubuntu运行 。 在生产环境中需要考虑服务器更好的性能,所以使用便携式发布(简单说就是服务器自带core环境)。 在当前项目右键进行发布到文件夹即可,默认路径为bin\Release\PublishOutput。 2.安装.NET Core SDK 这里就是配置centos的core环境,让我们的网站可以运行起来。 官方的安装文档:https://www.microsoft.com/net/learn/get-started/linuxcentos 在centos的右上角打开应用程序,找到终端并打开。 首先、Add the dotnet product feed(其实就是向微软提交投名状,表示我这台服务器要用core),注意:先检测下是否能联网; sudo rpm --import https://packages.microsoft.com/keys/microsoft.ascsudo sh -c 'echo -e "[packages-microsoft-com-prod]\nname=packages-microsoft-com-prod \nbaseurl= https://packages.microsoft.com/yumrepos/microsoft-rhel7.3-prod\nenabled=1\ngpgcheck=1\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc" > /etc/yum.repos.d/dotnetdev.repo' 然后,才安装 core SDK。 命令一:sudo yum update 需要一段时间,而且会提示是否继续,输入y然后回车。 如果遇到提示another app is currently holding the yum lock;waiting for it to exit,表示yum正在被使用,通过以下命令强制关闭: rm -f /var/run/yum.pid 命令二、sudo yum install libunwind libicu命令三、sudo yum install dotnet-sdk-2.0.2 这一步也需要下载安装包,耗时需要比较长时间。完成后使用命令 dotnet --help 查看下是否安装成功。 3.部署网站 首先,创建文件夹core,命令为 mkdir core cd core 我这里使用的是虚拟机,就直接将生成好的文件考到core目录下,然后执行命令(dotnet 项目名称.dll) dotnet DotNetCoreWebDemo.dll 直接使用自带的火狐浏览器就能访问http://localhost:5000 终于部署完了,由于整个过程都是使用图形界面,也没有遇到不懂命令的难题。 其实还有很多问题存在: 如何使用nginx? 怎么配置守护服务supervisor? 怎么使用docker ? 三、使用nginx 1.安装nginx 可以FQ的去看原文:https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-centos-7 1.添加 CentOS 7 EPEL 仓库 sudo yum install epel-release 2.安装nginx sudo yum install nginx 3.启动nginx sudo systemctl start nginx 4.使用ip访问,出现以下界面即成功安装 2、配制环境 1)配置防火墙 命令:firewall-cmd --zone=public --add-port=80/tcp --permanent(开放80端口) 命令:systemctl restart firewalld(重启防火墙以使配置即时生效) 2)关闭SELinux ,使用getenforce 查询 临时关闭(不用重启机器): setenforce 0 ##设置SELinux 成为permissive模式 ##setenforce 1 设置SELinux 成为enforcing模式 修改配置文件需要重启机器: 修改/etc/selinux/config 文件 将SELINUX=enforcing改为SELINUX=disabled 重启机器即可 3) 修改Nginx 配制,保存 命令: vim /etc/nginx/nginx.conf location / { proxy_pass http://localhost:5000; } 4) 重新加载Nginx配制文件 命令:nginx -t 测试配制文件是否正确 命令:nginx -s reload 重新加载nginx配制文件,不用重启nginx 第三步补充: 通过反向代理将ip访问指向到 http://localhost:5000 修改/etc/nginx/nginx.conf文件,在location / {} 中补充以下内容 proxy_pass http://localhost:5000/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection keep-alive; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 如果是多站点部署,在conf.d下创建一个core.conf,内容为 server{ ... } 注意:如果不修改第二步,会被selinux的安全设置拒绝,导致反向代理失败 ...... 还需努力,只是迈出了半步。。。 最后说一句,尽量把虚拟机搞的性能高一点,真是卡死我也。 参考文章: https://www.cnblogs.com/Leo_wl/p/5734988.html https://www.cnblogs.com/ares-yang/p/7736842.html https://www.cnblogs.com/hohoa/p/5691071.html https://www.cnblogs.com/ants/p/5732337.html https://www.cnblogs.com/Burt/p/6566642.html https://www.cnblogs.com/rabbityi/p/7019662.html http://blog.csdn.net/jollypigclub/article/details/46862371
上一篇简单介绍了日志的使用方法,也仅仅是用来做下学习,更何况只能在console输出。 NLog已是日志库的一员大佬,使用也简单方便,本文介绍的环境是居于.NET CORE 2.0 ,目前的版本也只有beta版。 一、安装和配置 1.安装 命令如下 PM> Install-Package NLog.Web.AspNetCore -Version 4.5.0-beta04 2.创建配置文件 在web项目根目录下,创建配置文件nlog.config ,并且将文件的属性“复制到输出目录”设置为"始终复制"。示例如下: <?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogLevel="info" internalLogFile="c:\temp\internal-nlog.txt"> <!-- the targets to write to --> <targets> <!-- write logs to file --> <target xsi:type="File" name="allfile" fileName="c:\temp\nlog-all-${shortdate}.log" layout="${longdate}|${event-properties:item=EventId.Id}|${uppercase:${level}}|${logger}|${message} ${exception}" /> <!-- another file log, only own logs. Uses some ASP.NET core renderers --> <target xsi:type="File" name="ownFile-web" fileName="c:\temp\nlog-own-${shortdate}.log" layout="${longdate}|${event-properties:item=EventId.Id}|${uppercase:${level}}|${logger}|${message} ${exception}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" /> <!-- write to the void aka just remove --> <target xsi:type="Null" name="blackhole" /> </targets> <!-- rules to map from logger name to target --> <rules> <!--All logs, including from Microsoft--> <logger name="*" minlevel="Trace" writeTo="allfile" /> <!--Skip Microsoft logs and so log only own logs--> <logger name="Microsoft.*" minlevel="Trace" writeTo="blackhole" final="true" /> <logger name="*" minlevel="Trace" writeTo="ownFile-web" /> </rules> </nlog> 日志级别 从高到低分为(如配置为info,使用logger.LogDebug和logger.LogTrace记录的日志将不会显示): Fatal Error Warn Info Debug Trace 日志输出格式 相关参数:https://github.com/NLog/NLog/wiki/Layout-renderers 3.初始化 更新program.cs,添加引用 using NLog.Web; 修改代码 public static void Main(string[] args) { // NLog: setup the logger first to catch all errors var logger = NLogBuilder.ConfigureNLog("NLog.config").GetCurrentClassLogger(); try { logger.Debug("init main"); BuildWebHost(args).Run(); } catch (Exception e) { //NLog: catch setup errors logger.Error(e, "Stopped program because of exception"); throw; } } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .UseNLog() // NLog: setup NLog for Dependency injection .Build(); 二、使用 配置好了之后,将ILogger注入到控制器就可以写日志了。 public class HomeController : Controller { private readonly ILogger<HomeController> _logger; public HomeController(ILogger<HomeController> logger) { _logger = logger; } public IActionResult Index() { _logger.LogInformation("Index page says hello"); return View(); } } 三、日志输出 访问刚才的index就可以在日志目录下看到2个生成的文件了。 nlog-all-2017-11-18.log 2017-11-18 23:09:02.2839||DEBUG|Tkx.Web.Program|init main 2017-11-18 23:09:02.6079||INFO|Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager|User profile is available. Using 'C:\Users\Administrator\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest. 2017-11-18 23:09:03.0070||INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request starting HTTP/1.1 POST http://localhost:59491/account/login application/x-www-form-urlencoded 26 2017-11-18 23:09:03.0320||INFO|Microsoft.AspNetCore.Cors.Infrastructure.CorsService|Policy execution successful. 2017-11-18 23:09:03.1720||INFO|Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker|Executing action method Tkx.Web.Controllers.AccountController.Login (Tkx.Web) with arguments (Tkx.WebApi.Models.User.UserLogin) - ModelState is Valid 2017-11-18 23:09:03.1760||INFO|Tkx.Web.Controllers.AccountController|================ 2017-11-18 23:09:03.1760||INFO|Tkx.Web.Controllers.AccountController|LOGIN IS START:admin 123 2017-11-18 23:09:03.1950||INFO|Tkx.Web.Controllers.AccountController|================ 2017-11-18 23:09:03.1950||INFO|Tkx.IBusiness.Implement.UserService|UserService param:admin 123 2017-11-18 23:09:03.3440||INFO|Tkx.IBusiness.Implement.UserService|UserService count:1 2017-11-18 23:09:03.3500||INFO|Tkx.IBusiness.Implement.UserService|UserService: 2017-11-18 23:09:03.3500||INFO|Microsoft.AspNetCore.Mvc.Formatters.Json.Internal.JsonResultExecutor|Executing JsonResult, writing value Tkx.Web.ResponseMessage. 2017-11-18 23:09:03.5290||INFO|Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker|Executed action Tkx.Web.Controllers.AccountController.Login (Tkx.Web) in 398.631ms 2017-11-18 23:09:03.5440||INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request finished in 543.2871ms 200 application/json; charset=utf-8 nlog-own-2017-11-18.log 2017-11-18 23:09:03.1760||INFO|Tkx.Web.Controllers.AccountController|================ |url: http://localhost/account/login|action: login 2017-11-18 23:09:03.1760||INFO|Tkx.Web.Controllers.AccountController|LOGIN IS START:admin 123 |url: http://localhost/account/login|action: login 2017-11-18 23:09:03.1950||INFO|Tkx.Web.Controllers.AccountController|================ |url: http://localhost/account/login|action: login 2017-11-18 23:09:03.1950||INFO|Tkx.IBusiness.Implement.UserService|UserService param:admin 123 |url: http://localhost/account/login|action: login 2017-11-18 23:09:03.3440||INFO|Tkx.IBusiness.Implement.UserService|UserService count:1 |url: http://localhost/account/login|action: login 2017-11-18 23:09:03.3500||INFO|Tkx.IBusiness.Implement.UserService|UserService: |url: http://localhost/account/login|action: login 在业务层使用方法一样简单,只要在业务层引入该库就可以。 如日志所显示的UserService就是我的业务层。 相关文档 项目地址:https://github.com/NLog/NLog.Web nlog使用方法https://github.com/NLog/NLog.Web/wiki/Getting-started-with-ASP.NET-Core-2配置文件的详细使用说明https://github.com/NLog/NLog/wiki/Configuration-file 其他关于nlog的文章: NLog类库使用探索——详解配置
一、简介 Selectize是一个可扩展的基于jQuery 的自定义下拉框的UI控件。它对展示标签、联系人列表、国家选择器等比较有用。它的大小在~ 7kb(gzip压缩)左右。提供一个可靠且体验良好的干净和强大的API。 功能介绍: 选项可查询和排序; 使用箭头键←和→在1️⃣选中选项之间移动; 对选择和删除项目可通过option/ctrl键多选 允许用户创建选项(包括异步); 远程数据加载; 干净的API和代码,接口化处理,易于修改; 可扩展强,使用插件API开发自定义功能; 触摸支持,支持iOS 5 +设备。 依赖: jquery (1.7 and greater) sifter (bundled in "standalone" build) microplugin (bundled in "standalone" build) 二、安装和引用 所有预编译的文件都在“dist”文件夹下,引入 standalone/selectize.min.js 和 css/selectize.default.css即可. 目录结构 js/standalone/selectize.js — With dependencies, minus jqueryselectize.js — Without dependenciesless/selectize.less — Core stylesselectize.default.less — Default themeselectize.bootstrap2.less — Bootstrap 2 themeselectize.bootstrap3.less — Bootstrap 3 themeplugins/ — Individual plugin stylescss/selectize.css — Core stylesselectize.default.css — Default theme (with core styles)selectize.bootstrap2.css - Bootstrap 2 themeselectize.bootstrap3.css - Bootstrap 3 theme 文档说明github:https://github.com/selectize/selectize.js配置: https://github.com/selectize/selectize.js/blob/master/docs/usage.md事件: https://github.com/selectize/selectize.js/blob/master/docs/events.mdAPI :https://github.com/selectize/selectize.js/blob/master/docs/api.md自定义插件:https://github.com/selectize/selectize.js/blob/master/docs/plugins.md 三、使用总结 以电话号码为例,做下总结 1.初始化 $("#select-telephone").selectize({ valueField: 'TelephoneNumber', labelField: 'TelephoneNumber', searchField: 'TelephoneNumber', create: true, render: { item: function (item, escape) { return '<span>' + escape(item.TelephoneNumber) + '</span>'; }, option: function (item, escape) { return fortmatTelephone(item.TelephoneNumber, item.ShortNumber); } }, load: function (query, callback) { // console.log(query);//可实时远程查询 //callback(data); } }); function fortmatTelephone(tel, shortTel) { var markup = '<div class="show-select-option">'; if (!isEmpty(tel)) { shortTel = isEmpty(shortTel) ? "" : ' (' + shortTel + ")"; markup += '<p class="text-primary">电话:' + tel + shortTel + '</p>'; } return markup + '</div>'; } create: 允许增加下拉选项,输入后按回车即可;render:item和option的值需要使用HTML标签进行格式化;load: query为在输入框输入的值,需要注意的时,如果你输入的值在之前就搜索过,那么它不会再执行该方法。 2.下拉关闭事件 selectize.on('dropdown_close', function ($dropdown) { if (closeCount % 2 === 0) { //执行2次了,通过closeCount变量来消除bug } closeCount += 1; }); 3.选中值 setValue(value, silent): 设置选中值,注意这个值必须已在下拉列表中;setTextboxValue: 设置文本框值,并非选中的值;getValue(): 获取选中的值;focus(): 聚焦后下拉框自动展开; clear(): 清空选中 telephoneSelectize.setValue('12345678', true); telephoneSelectize.setTextboxValue('1111'); telephoneSelectize.focus();telephoneSelectize.clear(); 4.启用禁用 var $selectTelephone = $("#select-telephone").selectize(); telephoneSelectize = $selectTelephone[0].selectize; telephoneSelectize.disable(); telephoneSelectize.enable(); 5.添加下拉选项 telephoneSelectize.addOption(telArray); telephoneSelectize.enable(); 可以增加一个选项,也可增加一个数组,如果已经存在不会变化。此操作不会刷新下拉框选项,需要使用refreshOptions() 进行刷新
对于像我这样没接触过core的人,坑还是比较多的,一些基础配置和以前差别很大,这里做下记录 一、Startup 1.注册服务 // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc(); // services.AddTransient<IUser, User>(); //services.AddSingleton<IUser>(new User()); services.AddSingleton<IUser, User>(); //services.AddScoped<>();//作用域注入 services.AddMemoryCache(); //MemoryCache缓存注入 } 2.配置HTTP请求管道 听起来很蒙,其实就是用于处理我们程序中的各种中间件,它必须接收一个IApplicationBuilder参数,我们可以手动补充IApplicationBuilder的Use扩展方法,将中间件加到Configure中,用于满足我们的需求。 // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } 3.自定义配置文件 类似于web.config ///IHostingEnvironment获取环境变量信息,没错就是获取环境变量 public Startup(IHostingEnvironment env) { //这里创建ConfigurationBuilder,其作用就是加载Congfig等配置文件 var builder = new ConfigurationBuilder() //env.ContentRootPath:获取当前项目的跟路径 .SetBasePath(env.ContentRootPath) //使用AddJsonFile方法把项目中的appsettings.json配置文件加载进来,后面的reloadOnChange顾名思义就是文件如果改动就重新加载 .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) .AddEnvironmentVariables(); //这返回一个配置文件跟节点:IConfigurationRoot Configuration = builder.Build(); } 二、Program 1. core1.0和2.0对比 core1.0和2.0的program写法是不一样的 .net core 1.0 public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup<Startup>() .UseApplicationInsights() .Build(); host.Run(); } .net core 2.0 public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .Build(); 看一下WebHost.CreateDefaultBuilder(args)的源码: public static IWebHostBuilder CreateDefaultBuilder(string[] args) {var builder = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .ConfigureAppConfiguration((hostingContext, config) => { var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); if (env.IsDevelopment()) { var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); if (appAssembly != null) { config.AddUserSecrets(appAssembly, optional: true); } } config.AddEnvironmentVariables(); if (args != null) { config.AddCommandLine(args); } }) .ConfigureLogging((hostingContext, logging) => { logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); }) .UseIISIntegration() .UseDefaultServiceProvider((context, options) => { options.ValidateScopes = context.HostingEnvironment.IsDevelopment(); }); return builder; } 可以看到2.0不过是被封装了,还集成了appsettings和日志的初始化。 sp.net core 自带了两种http servers, 一个是WebListener,在2.0中重命名为HTTP.sys , 可以使用来实现,它只能用于windows系统, 另一个是kestrel, 它是跨平台的. 2. Kestrel 支持特性 HTTPS Opaque upgrade used to enable WebSockets Unix sockets for high performance behind Nginx kestrel是默认的web server, 就是通过UseKestrel()这个方法来启用的. 但是我们开发的时候使用的是IIS Express, 调用UseIISIntegration(),启用IIS Express, 它作为Kestrel的Reverse Proxy server来用; 如果在windows服务器上部署的话, 就应该使用IIS作为Kestrel的反向代理服务器来管理和代理请求,和原IIS工作进程是分开的,通过ASP.NET Core Module (ANCM) 来控制core程序; 此图 说明了 IIS, ANCM 和ASP.NET Core 应用程序直接的关系 Requests come in from the Web and hit the kernel mode Http.Sys driver which routes them into IIS on the primary port (80) or SSL port (443). ANCM forwards the requests to the ASP.NET Core application on the HTTP port configured for the application, which is not port 80/443. Kestrel listens for traffic coming from ANCM. ANCM specifies the port via environment variable at startup, and the UseIISIntegration method configures the server to listen on http://localhost:{port}. There are additional checks to reject requests not from ANCM. (ANCM does not support HTTPS forwarding, so requests are forwarded over HTTP even if received by IIS over HTTPS.) 注:大体意思是从80/443端口进来,然后ANCM通过 指定的端口 与程序进行交互,其中IISIntegration通过监听该端口,拒绝掉不是来自ANCM的请求。 如果在linux上的话, 可以使用apache, nginx等等的作为kestrel的proxy server,使用ForwardedHeaders中间件做处理,具体可以看官网怎么在linux下部署的文档; 当然也可以单独使用kestrel作为web 服务器, 但是使用iis作为reverse proxy还是由很多有点的: 例如,IIS可以过滤请求, 管理证书, 程序崩溃时自动重启等. 3. HTTP.sys HTTP.sys, 是通过UseKestrel()这个方法来启用的。但它不能与IIS 或者IIS Express 使用,也不能使用ANCM。 HTTP.sys 支持以下特性: Windows Authentication Port sharing HTTPS with SNI HTTP/2 over TLS (Windows 10) Direct file transmission Response caching WebSockets (Windows 8) 支持的Windows 版本: Windows 7 and Windows Server 2008 R2 and later 三、使用日志 public class HomeController : Controller { public HomeController() { ILoggerFactory loggerFactory = new LoggerFactory().AddConsole().AddDebug(); _logger = loggerFactory.CreateLogger<HomeController>(); _logger.LogInformation("================"); _logger.LogInformation("LOGGER IS START"); _logger.LogInformation("================"); } } 四、Routing 路由有两种方式: Convention-based (按约定), attribute-based(基于路由属性配置的). 其中convention-based (基于约定的) 主要用于MVC (返回View或者Razor Page那种的). Web api 推荐使用attribute-based. 这种基于属性配置的路由可以配置Controller或者Action级别, uri会根据Http method然后被匹配到一个controller里具体的action上. 常用的Http Method有: Get, 查询, Attribute: HttpGet, 例如: '/api/product', '/api/product/1' POST, 创建, HttpPost, '/api/product' PUT 整体修改更新 HttpPut, '/api/product/1' PATCH 部分更新, HttpPatch, '/api/product/1' DELETE 删除, HttpDelete, '/api/product/1 还有一个Route属性(attribute)也可以用于Controller层, 它可以控制action级的URI前缀. 问题:继承api控制器后,只能使用Http Method,不能使用其他名称,或者是我不懂运用,比如GetUser 参考: http://blog.csdn.net/sd7o95o/article/details/78190862 http://blog.csdn.net/sd7o95o/article/details/78190862
一、入门简介 在学习之前,要先了解ASP.NET Core是什么?为什么?很多人学习新技术功利心很重,恨不得立马就学会了。 其实,那样做很不好,马马虎虎,联系过程中又花费非常多的时间去解决所遇到的“问题”,是简单的问题,对,就是简单,就是因为觉得简单被忽略的东西,恰恰这才是最重要的。 1、学习资料 首先,介绍下哪里可以获得学习资料 英文官网,最好的文档,英语得过硬 https://docs.microsoft.com/en-us/aspnet/core/ 可惜当年英语就是马马虎虎过来的,所以找了以下中午翻译,以下是中午目录的链接 http://www.cnblogs.com/dotNETCoreSG/p/aspnetcore-index.html 以下开始正题,主要来自dotNETCoreSG博客,对有用的内容进行删减记录,少花时间看一些不想看的东西 2、什么是 ASP.NET Core? ASP.NET Core 是一个新的开源和跨平台的框架,用于构建如 Web 应用、物联网(IoT)应用和移动后端应用等连接到互联网的基于云的现代应用程序。ASP.NET Core 应用可运行于 .NET Core 和完整的 .NET Framework 之上。 构建它的目的是为那些部署在云端或者内部运行(on-premises)的应用提供一个优化的开发框架。它由最小开销的模块化的组件构成,因此在构建你的解决方案的同时可以保持灵活性。你可以在 Windows、Mac 和 Linux 上跨平台的开发和运行你的 ASP.NET Core 应用。 ASP.NET Core 开源在 GitHub 上。 3、为什么构建 ASP.NET Core? ASP.NET Core 有一些架构上的改变,不再基于 System.Web.dll 。当前它基于一系列颗粒化的,并且良好构建的 NuGet 包。这一特点能够让你通过仅仅包含需要的 NuGet 包的方法来优化你的应用。通过 ASP.NET Core,你可以获得的改进: 一个统一的方式用于构建 web UI 和 web APIs 集成 现代的客户端开发框架 和开发流程 一个适用于云的,基于环境的 配置系统 内置的 依赖注入 新型的轻量级的、模块化 HTTP 请求管道 运行于 IIS 或者自宿主(self-host)于你自己的进程的能力 基于支持真正的 side-by-side 应用程序版本化的 .NET Core 构建 完全以 NuGet 包的形式发布 新的用于简化现代 web 开发的工具 可以在 Windows 、Mac 和 Linux 上构建和运行跨平台的 ASP.NET 应用 开源并且重视社区 二、入门基础 1.应用程序剖析 一个 ASP.NET Core 应用其实就是一个在其 Main 方法中创建一个 web 服务器的简单应用程序: using System; using Microsoft.AspNetCore.Hosting; namespace aspnetcoreapp { public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseStartup<Startup>() .Build(); host.Run(); } } } Main 调用遵循 builder 模式的 WebHostBuilder ,用于创建一个 web 应用程序宿主。这个 builder 有些用于定义 web 服务器 (如 UseKestrel)和 startup 类型( UseStartup)的方法。在上面的示例中,web 服务器 Kestrel 被启用,但是你也可以指定其它 web 服务器。我们将会在下一节展示更多关于 UseStartup 的内容。WebHostBuilder 提供了一些可选方法,其中包括寄宿在 IIS 和 IIS Express 中的 UseIISIntegration 和用于指定根内容目录的 UseContentRoot。Build 和 Run 方法构建了用于宿主应用程序的 IWebHost 然后启动它来监听传入的 HTTP 请求。 WebHostBuilder 的 UseStartup 方法为你的应用指定了 Startup 类。 Startup 类是用来定义请求处理管道和配置应用需要的服务。 Startup 类必须是公开的(public)并且包含如下方法: public class Startup { public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app) { } } ConfigureServices 定义你的应用所使用的服务(在下面查看 服务(Services) )(例如 ASP.NET MVC Core framework、Entity Framework Core、Identity 等等) Configure 定义你的请求管道中的 中间件(middleware) 更多内容请参考: Application Startup 服务(Services)是应用中用于通用调用的组件。服务通过依赖注入获取并使用。 ASP.NET Core 内置了一个简单的控制反转(IoC) 容器,它默认支持构造器注入,并且可以方便的替换成你自己选用的 IoC 容器。由于它的松耦合特性,依赖注入(DI) 使服务在整个应用中都可以使用。例如,Logging 在你整个应用中都可用。查看 Dependency Injection 获取更多信息。 中间件(Middleware) 在 ASP.NET Core 中,你可以使用 Middleware 构建你的请求处理管道。 ASP.NET Core 中间件为一个 HttpContext 执行异步逻辑,然后按顺序调用下一个中间件或者直接终止请求。一般来说你要使用一个中间件,只需要在 Configure 方法里调用 IApplicationBuilder 上一个对应的 UseXYZ 扩展方法。 ASP.NET Core 带来了丰富的内置中间件: 静态文件(Static files) 路由(Routing) 身份验证(Authentication) 你也可以创建你自己的 自定义中间件。 你也可以在 ASP.NET Core 中使用任何基于 OWIN 的中间件。查看 OWIN 获取更多信息。 服务器(Servers) ASP.NET Core 托管模式并不直接监听请求;而是依赖于一个 HTTP server 实现来转发请求到应用程序。这个被转发的请求会以一组 feature 接口的形式被包装,然后被应用程序组合到一个 HttpContext中去。 ASP.NET Core 包含了一个托管的跨平台 web 服务器,被称为 Kestrel,它往往会被运行在一个如 IIS 或者 nginx 的生产 web 服务器之后。 内容根目录(Content root)是应用程序所用到的所有内容的根路径,例如它的 views 和 web 内容。内容根目录默认与宿主应用的可执行程序的应用根目录相同;一个替代的地址可以通过 WebHostBuilder 来设置。 你的应用的Web根目录(Web root)是你项目中所有公共的、静态的资源,如 css、js 和 图片文件的目录。静态文件中间件将默认只发布 Web 根目录(Web root)和其子目录中的文件。 Web 根目录(Web root)默认为 /wwwroot,但是你也可以通过 WebHostBuilder 来指定另外一个地址。没明白好处在哪里,和以前有何不同? 配置(Configuration) ASP.NET Core 使用了一个新的配置模型用于处理简单的键值对。新的配置模型并非基于System.Configuration 或者 web.config ;而是从一个有序的配置提供者集合拉取数据。内置的配置提供者支持多种不同的文件格式如(XML,JSON, INI)和用于支持基于环境的配置环境变量。你也可以实现你自己自定义的配置提供者。查看 Configuration 获取更多信息。 环境(Environments),如 “Development” 和 “Production”,是 ASP.NET Core 中的第一级概念而且它可以设置成使用环境变量。查看 Working with Multiple Environments 获取更多信息。 使用 ASP.NET Core MVC 构建 web UI 和 web APIs 你可以使用 Model-View-Controller(MVC)模式创建优秀的并且可测试的 web 应用程序。查看 MVC 和 测试。 你可以构建支持多种格式并且完全支持内容协商的 HTTP 服务。 查看 Formatting Razor 提供了一种高效的语言用于创建 Views Tag Helpers 启用服务器端的代码参与到 - Razor 文件的创建和 HTML 元素渲染 你可以使用自定义或者内置的 formatters (JSON, XML)来构建完全支持内容协商的 HTTP 服务 Model Binding 模型绑定 自动的映射 HTTP 请求中的数据到 action 方法参数 Model Validation 模型验证 自动的执行客户端和服务器端验证 客户端开发 ASP.NET Core 在设计时已考虑到和各种客户端框架(AngularJS,KnockoutJS 和 Bootstrap)的无缝集成。查看 Client-Side Development 获取更多信息。
一、简介 所使用过的弹出框插件,SweetAlert是最好用的。发展至今,已经有两个版本,一个是原版 t4t5/sweetalert , 一个是分支版 limonte/sweetalert2 ,更新相对较快,听说更好看。 SweetAlert是一款不需要jQuery支持的原生js提示框,风格类似bootstrap。现在都已经升级到2.0 它的提示框不仅美丽动人,并且允许自定义,支持设置提示框标题、提示类型、内容展示图片、确认取消按钮文本、点击后回调函数等。 使用非常简单,示例如下 swal("Hello world!"); swal("Here's the title!", "...and here's the text!"); //包含子标题 swal("Good job!", "You clicked the button!", "success");//包含图标 二、t4t5/sweetalert github地址:https://github.com/t4t5/sweetalert 官方文档: https://sweetalert.js.org/docs/ 中文: http://mishengqiang.com/sweetalert/ http://mishengqiang.com/sweetalert/ http://www.sucaihuo.com/jquery/12/1241/demo/ 文档中没有浏览器兼容提示,只能自己测试了 三、limonte/sweetalert2 github地址:https://github.com/limonte/sweetalert2 在线实例:https://limonte.github.io/sweetalert2 中文:http://www.jq22.com/jquery-info8169 浏览器兼容 IE11* Edge Chrome Firefox Safari Opera Android Browser* UC Browser* * ES6 Promise polyfill should be included, see usage example. SweetAlert2 不再支持ie10及以下版本
注意点: 1.mvc的控制器必须继承于ApiController,否则看不到的 2. 简单查看xml,根据第二步即可生成xml,可视感比较低,但是内容全部有,不限定于Api 以下为转载正文 ======================================================================================== 新建Web Api项目之后,会在首页有API的导航菜单,点击即可看到API帮助文档,不过很遗憾,Description 是没有内容的。 怎么办呢? 第一步: 如果用VS2013 新建项目的(VS2012没试过),项目中会有 Areas/HelpPage 这样的目录,你没看错,文档就是这货生成的。 如果要是删除了或者,没有这个目录怎么办呢?没关系,你只需要使用NuGet添加 【Microsoft.AspNet.WebApi.HelpPage】这货,然后你就发现,你的项目自动添加了 Areas/HelpPage这一坨 第二步: 选中项目,右键,属性,生成,选择下面的XML 文档文件,目录自己填写,如下图: 第三步: 找到 Areas/HelpPage/App_Start 目录下的HelpPageConfig.cs 文件,Register 方法,添加一行代码: 1 config.SetDocumentationProvider(new XmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/Areas/HelpPage/WebApiHelp.XML"))); 然后生成一下,启动项目,点击API看看 然后你就会感觉,自己棒棒哒。。。 Ps: 生成API 文档说明的前提是你的 写注释!!!!! 来自 http://blog.csdn.net/shiyaru1314/article/details/49995547 相关文章推荐 http://www.cnblogs.com/pmars/p/3673654.html
展示: 实现方法: 1.html引用star-grade.js <script type="text/javascript" src="Scripts/star-grade.js"></script> <script type="text/javascript"> $(document).ready(function () { $(".sstar").BindStars();//使用属性data-score获取评分值 $("#pye").SetStars(3);//传分数,自动列出星星 }); </script> <body> <div class="starscore sstar"> <i></i> <i></i> <i></i> <i></i> <i></i> </div> <span id="ye"></span> <div class="starscore" id="pye"></div> <div class="starscore starscore_sm" > <i class="inred"></i> <i class="inred"></i> <i></i> <i></i> <i></i> </div> <div class="starscore starscore_lg"> <i class="onred"></i> <i class="onred"></i> <i></i> <i></i> <i></i> </div> </body> 2.css样式 @charset "utf-8"; /* CSS Document */ .starscore { width: 200px; height: 30px; } .starscore i { width: 14px; height: 14px; background: url(images/gray.gif) no-repeat; /* 14x14的灰色星星图标 */ display: inline-block; vertical-align: middle; background-size: contain; } .starscore i.inred, .starscore i.onred { background: url(images/yel.gif) no-repeat; /* 14x14的黄色星星图标 */ } .starscore_lg > i { width: 18px; height: 18px; } .starscore_sm > i { width: 12px; height: 12px; } 3.插件js源码 /* * jq扩展--星星评分插件 * * 通过对象的属性data-score获取评分值 * 星星使用元素i表示,绑定星星背景图 * 鼠标进入、离开事件的绑定样式为inred,改变背景图 * 点击事件的绑定样式为onred,改变背景图 */ (function ($) { "use strict"; $.fn.BindStars = function () { var starElement = $(this); starElement.children("i").mouseleave(function () { starElement.find("i").each(function (index) { $(this).removeClass("inred"); }); }); starElement.children("i").mouseenter(function () { var curIndex = starElement.find("i").index(this); starElement.find("i").each(function (index) { if (index <= curIndex) { $(this).addClass("inred"); } else { $(this).removeClass("inred"); } }); }); starElement.children("i").click(function () { var curIndex = starElement.find("i").index(this); starElement.find("i").each(function (index) { if (index <= curIndex) { $(this).addClass("onred"); } else { $(this).removeClass("onred"); } }); starElement.attr("data-score", curIndex + 1); }); }; $.fn.SetStars = function (score) { var scoreStr = ""; for (var i = 0; i < 5; i++) { if (i < score) { scoreStr += "<i class='onred'></i>"; } else { scoreStr += "<i></i>"; } } $(this).html(scoreStr); }; })(window.jQuery);
在MSSQL Server中通过查看SQL语句执行所用的时间,来衡量SQL语句的性能。 通过设置STATISTICS我们可以查看执行SQL时的系统情况。选项有PROFILE,IO ,TIME。介绍如下: SET STATISTICS PROFILE ON:显示分析、编译和执行查询所需的时间(以毫秒为单位)。 SET STATISTICS IO ON:报告与语句内引用的每个表的扫描数、逻辑读取数(在高速缓存中访问的页数)和物理读取数(访问磁盘的次数)有关的信息。 SET STATISTICS TIME ON:显示每个查询执行后的结果集,代表查询执行的配置文件。 方法一 手动计算 先记录执行前的时间,然后在记录执行Sql后的时间,然后做减法 declare @d datetime set @d=getdate() /*你的SQL脚本开始*/ /*你的SQL脚本结束*/ select [语句执行花费时间(毫秒)]=datediff(ms,@d,getdate()) 这种方法感觉不够准确,和第二种方法对比,时间显示比较长,可能是操作时间的相关函数导致的吧 方法二 使用sql开启自带统计 将执行每个语句时采取的步骤作为行集返回,通过层次结构树的形式展示出来 SET STATISTICS PROFILE ON SET STATISTICS IO ON SET STATISTICS TIME ON GO /*--你的SQL脚本开始*/ /*你的SQL脚本结束*/ GO SET STATISTICS PROFILE OFF SET STATISTICS IO OFF SET STATISTICS TIME OFF 第2个方法效果如下图, 方法三 用Sql Server 自带的工具 位置:工具》选项》查询执行》高级 效果如图, 原文:http://www.cnblogs.com/wangguowen27/p/SqlServer_Select_wgw.html 相关: http://blog.csdn.net/hj850126/article/details/7161028 http://www.cnblogs.com/qanholas/archive/2011/05/06/2038543.html
原文:http://www.cnblogs.com/aspnethot/articles/1504082.html 索引 官方说法: 聚集索引 一种索引,该索引中键值的逻辑顺序决定了表中相应行的物理顺序。 聚集索引确定表中数据的物理顺序。聚集索引类似于电话簿,后者按姓氏排列数据。由于聚集索引规定数据在表中的物理存储顺序,因此一个表只能包含一个聚集索引。但该索引可以包含多个列(组合索引),就像电话簿按姓氏和名字进行组织一样。 聚集索引对于那些经常要搜索范围值的列特别有效。使用聚集索引找到包含第一个值的行后,便可以确保包含后续索引值的行在物理相邻。例如,如果应用程序执行 的一个查询经常检索某一日期范围内的记录,则使用聚集索引可以迅速找到包含开始日期的行,然后检索表中所有相邻的行,直到到达结束日期。这样有助于提高此 类查询的性能。同样,如果对从表中检索的数据进行排序时经常要用到某一列,则可以将该表在该列上聚集(物理排序),避免每次查询该列时都进行排序,从而节 省成本。 当索引值唯一时,使用聚集索引查找特定的行也很有效率。例如,使用唯一雇员 ID 列 emp_id 查找特定雇员的最快速的方法,是在 emp_id 列上创建聚集索引或 PRIMARY KEY 约束。 非聚集索引 一种索引,该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同。 索引是通过二叉树的数据结构来描述的,我们可以这么理解聚簇索引:索引的叶节点就是数据节点。而非聚簇索引的叶节点仍然是索引节点,只不过有一个指针指向对应的数据块。如下图: (非聚集索引) (聚集索引) 一、深入浅出理解索引结构 实际上,您可以把索引理解为一种特殊的目录。微软的SQL SERVER提供了两种索引:聚集索引(clustered index,也称聚类索引、簇集索引)和非聚集索引(nonclustered index,也称非聚类索引、非簇集索引)。下面,我们举例来说明一下聚集索引和非聚集索引的区别: 其实,我们的汉语字典的正文本身就是一个聚集索引。比如,我们要查“安”字,就会很自然地翻开字典的前几页,因为“安”的拼音是“an”,而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的,那么“安”字就自然地排在字典的前部。如果您翻完了所有以“a”开头的部分仍然找不到这个字,那么就说明您的字典中没有这个字;同样的,如果查“张”字,那您也会将您的字典翻到最后部分,因为“张”的拼音是“zhang”。也就是说,字典的正文部分本身就是一个目录,您不需要再去查其他目录来找到您需要找的内容。我们把这种正文内容本身就是一种按照一定规则排列的目录称为“聚集索引”。 如果您认识某个字,您可以快速地从自动中查到这个字。但您也可能会遇到您不认识的字,不知道它的发音,这时候,您就不能按照刚才的方法找到您要查的字,而需要去根据“偏旁部首”查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”和“检字表”而查到的字的排序并不是真正的正文的排序方法,比如您查“张”字,我们可以看到在查部首之后的检字表中“张”的页码是672页,检字表中“张”的上面是“驰”字,但页码却是63页,“张”的下面是“弩”字,页面是390页。很显然,这些字并不是真正的分别位于“张”字的上下方,现在您看到的连续的“驰、张、弩”三字实际上就是他们在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我们可以通过这种方式来找到您所需要的字,但它需要两个过程,先找到目录中的结果,然后再翻到您所需要的页码。我们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为“非聚集索引”。 通过以上例子,我们可以理解到什么是“聚集索引”和“非聚集索引”。进一步引申一下,我们可以很容易的理解:每个表只能有一个聚集索引,因为目录只能按照一种方法进行排序。 二、何时使用聚集索引或非聚集索引 下面的表总结了何时使用聚集索引或非聚集索引(很重要): 动作描述 使用聚集索引 使用非聚集索引 列经常被分组排序 应 应 返回某范围内的数据 应 不应 一个或极少不同值 不应 不应 小数目的不同值 应 不应 大数目的不同值 不应 应 频繁更新的列 不应 应 外键列 应 应 主键列 应 应 频繁修改索引列 不应 应 事实上,我们可以通过前面聚集索引和非聚集索引的定义的例子来理解上表。如:返回某范围内的数据一项。比如您的某个表有一个时间列,恰好您把聚合索引建立在了该列,这时您查询2004年1月1日至2004年10月1日之间的全部数据时,这个速度就将是很快的,因为您的这本字典正文是按日期进行排序的,聚类索引只需要找到要检索的所有数据中的开头和结尾数据即可;而不像非聚集索引,必须先查到目录中查到每一项数据对应的页码,然后再根据页码查到具体内容。 三、结合实际,谈索引使用的误区 理论的目的是应用。虽然我们刚才列出了何时应使用聚集索引或非聚集索引,但在实践中以上规则却很容易被忽视或不能根据实际情况进行综合分析。下面我们将根据在实践中遇到的实际问题来谈一下索引使用的误区,以便于大家掌握索引建立的方法。 1、主键就是聚集索引 这种想法笔者认为是极端错误的,是对聚集索引的一种浪费。虽然SQL SERVER默认是在主键上建立聚集索引的。 通常,我们会在每个表中都建立一个ID列,以区分每条数据,并且这个ID列是自动增大的,步长一般为1。我们的这个办公自动化的实例中的列Gid就是如此。此时,如果我们将这个列设为主键,SQL SERVER会将此列默认为聚集索引。这样做有好处,就是可以让您的数据在数据库中按照ID进行物理排序,但笔者认为这样做意义不大。 显而易见,聚集索引的优势是很明显的,而每个表中只能有一个聚集索引的规则,这使得聚集索引变得更加珍贵。 从我们前面谈到的聚集索引的定义我们可以看出,使用聚集索引的最大好处就是能够根据查询要求,迅速缩小查询范围,避免全表扫描。在实际应用中,因为 ID号是自动生成的,我们并不知道每条记录的ID号,所以我们很难在实践中用ID号来进行查询。这就使让ID号这个主键作为聚集索引成为一种资源浪费。其次,让每个ID号都不同的字段作为聚集索引也不符合“大数目的不同值情况下不应建立聚合索引”规则;当然,这种情况只是针对用户经常修改记录内容,特别是索引项的时候会负作用,但对于查询速度并没有影响。 在办公自动化系统中,无论是系统首页显示的需要用户签收的文件、会议还是用户进行文件查询等任何情况下进行数据查询都离不开字段的是“日期”还有用户本身的“用户名”。 通常,办公自动化的首页会显示每个用户尚未签收的文件或会议。虽然我们的where语句可以仅仅限制当前用户尚未签收的情况,但如果您的系统已建立了很长时间,并且数据量很大,那么,每次每个用户打开首页的时候都进行一次全表扫描,这样做意义是不大的,绝大多数的用户1个月前的文件都已经浏览过了,这样做只能徒增数据库的开销而已。事实上,我们完全可以让用户打开系统首页时,数据库仅仅查询这个用户近3个月来未阅览的文件,通过“日期”这个字段来限制表扫描,提高查询速度。如果您的办公自动化系统已经建立的2年,那么您的首页显示速度理论上将是原来速度8倍,甚至更快。 在这里之所以提到“理论上”三字,是因为如果您的聚集索引还是盲目地建在ID这个主键上时,您的查询速度是没有这么高的,即使您在“日期”这个字段上建立的索引(非聚合索引)。下面我们就来看一下在1000万条数据量的情况下各种查询的速度表现(3个月内的数据为25万条): (1)仅在主键上建立聚集索引,并且不划分时间段: Select gid,fariqi,neibuyonghu,title from tgongwen 用时:128470毫秒(即:128秒) (2)在主键上建立聚集索引,在fariq上建立非聚集索引: select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi> dateadd(day,-90,getdate()) 用时:53763毫秒(54秒) (3)将聚合索引建立在日期列(fariqi)上: select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi> dateadd(day,-90,getdate()) 用时:2423毫秒(2秒) 虽然每条语句提取出来的都是25万条数据,各种情况的差异却是巨大的,特别是将聚集索引建立在日期列时的差异。事实上,如果您的数据库真的有1000 万容量的话,把主键建立在ID列上,就像以上的第1、2种情况,在网页上的表现就是超时,根本就无法显示。这也是我摒弃ID列作为聚集索引的一个最重要的因素。得出以上速度的方法是:在各个select语句前加: declare @d datetime set @d=getdate() 并在select语句后加: select [语句执行花费时间(毫秒)]=datediff(ms,@d,getdate()) 2、只要建立索引就能显著提高查询速度 事实上,我们可以发现上面的例子中,第2、3条语句完全相同,且建立索引的字段也相同;不同的仅是前者在fariqi字段上建立的是非聚合索引,后者在此字段上建立的是聚合索引,但查询速度却有着天壤之别。所以,并非是在任何字段上简单地建立索引就能提高查询速度。 从建表的语句中,我们可以看到这个有着1000万数据的表中fariqi字段有5003个不同记录。在此字段上建立聚合索引是再合适不过了。在现实中,我们每天都会发几个文件,这几个文件的发文日期就相同,这完全符合建立聚集索引要求的:“既不能绝大多数都相同,又不能只有极少数相同”的规则。由此看来,我们建立“适当”的聚合索引对于我们提高查询速度是非常重要的。 3、把所有需要提高查询速度的字段都加进聚集索引,以提高查询速度 上面已经谈到:在进行数据查询时都离不开字段的是“日期”还有用户本身的“用户名”。既然这两个字段都是如此的重要,我们可以把他们合并起来,建立一个复合索引(compound index)。 很多人认为只要把任何字段加进聚集索引,就能提高查询速度,也有人感到迷惑:如果把复合的聚集索引字段分开查询,那么查询速度会减慢吗?带着这个问题,我们来看一下以下的查询速度(结果集都是25万条数据):(日期列fariqi首先排在复合聚集索引的起始列,用户名neibuyonghu排在后列): (1)select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>''2004-5-5'' 查询速度:2513毫秒 (2)select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>''2004-5-5'' and neibuyonghu=''办公室'' 查询速度:2516毫秒 (3)select gid,fariqi,neibuyonghu,title from Tgongwen where neibuyonghu=''办公室'' 查询速度:60280毫秒 从以上试验中,我们可以看到如果仅用聚集索引的起始列作为查询条件和同时用到复合聚集索引的全部列的查询速度是几乎一样的,甚至比用上全部的复合索引列还要略快(在查询结果集数目一样的情况下);而如果仅用复合聚集索引的非起始列作为查询条件的话,这个索引是不起任何作用的。当然,语句1、2的查询速度一样是因为查询的条目数一样,如果复合索引的所有列都用上,而且查询结果少的话,这样就会形成“索引覆盖”,因而性能可以达到最优。同时,请记住:无论您是否经常使用聚合索引的其他列,但其前导列一定要是使用最频繁的列。 四、书上没有的索引使用经验总结 1、用聚合索引比用不是聚合索引的主键速度快 下面是实例语句:(都是提取25万条数据) select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi=''2004-9-16'' 使用时间:3326毫秒 select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid<=250000 使用时间:4470毫秒 这里,用聚合索引比用不是聚合索引的主键速度快了近1/4。 2、用聚合索引比用一般的主键作order by时速度快,特别是在小数据量情况下 select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by fariqi 用时:12936 select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by gid 用时:18843 这里,用聚合索引比用一般的主键作order by时,速度快了3/10。事实上,如果数据量很小的话,用聚集索引作为排序列要比使用非聚集索引速度快得明显的多;而数据量如果很大的话,如10万以上,则二者的速度差别不明显。 3、使用聚合索引内的时间段,搜索时间会按数据占整个数据表的百分比成比例减少,而无论聚合索引使用了多少个: select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>''2004-1-1'' 用时:6343毫秒(提取100万条) select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>''2004-6-6'' 用时:3170毫秒(提取50万条) select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi=''2004-9-16'' 用时:3326毫秒(和上句的结果一模一样。如果采集的数量一样,那么用大于号和等于号是一样的) select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>''2004-1-1'' and fariqi<''2004-6-6'' 用时:3280毫秒 4、日期列不会因为有分秒的输入而减慢查询速度 下面的例子中,共有100万条数据,2004年1月1日以后的数据有50万条,但只有两个不同的日期,日期精确到日;之前有数据50万条,有5000个不同的日期,日期精确到秒。 select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>''2004-1-1'' order by fariqi 用时:6390毫秒 select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi<''2004-1-1'' order by fariqi 用时:6453毫秒 五、其他注意事项 “水可载舟,亦可覆舟”,索引也一样。索引有助于提高检索性能,但过多或不当的索引也会导致系统低效。因为用户在表中每加进一个索引,数据库就要做更多的工作。过多的索引甚至会导致索引碎片。 所以说,我们要建立一个“适当”的索引体系,特别是对聚合索引的创建,更应精益求精,以使您的数据库能得到高性能的发挥。 当然,在实践中,作为一个尽职的数据库管理员,您还要多测试一些方案,找出哪种方案效率最高、最为有效。
以前写了一些关于sql的文章,包括一些转载的,这里做下整理,方便需要时候使用 一、基础运用 SQL 数据结构操作语句 SQL 时间处理 SQL 常见函数使用 CASE WHEN THEN 小结 二、优化 三、排错 查看SqlServer的内存使用情况 四、数据建模工具 PowerDesigner使用总结(转) PowerDesigner 16安装注意事项 PowerDesigner V16.5 安装文件 及 破解文件 PowerDesigner的Table视图同时显示Code和Name的方法[转发]
本文主要描述如何开启各个浏览器的桌面通知功能 一、谷歌浏览器(chrome) 点击地址栏前面的图标 或者 ⓘ,修改通知即可 二、360浏览器 在地址栏输入 se://settings/content,找到通知进行设置 三、火狐浏览器(Mozilla Firefox) 您可以通过下列步骤设置站点的权限: 点击图标 打开 控制中心。 点击提示中的箭头。 点击 更多信息 打开页面信息窗口。 点击 权限 标签页。 在 接收通知 部分,选择:总是询问、允许 或 阻止。如果选项灰显,请取消勾选 使用默认 旁边的勾选框。 详情查看官方文档:https://support.mozilla.org/zh-CN/kb/%E7%BD%91%E9%A1%B5%E6%8E%A8%E9%80%81%E9%80%9A%E7%9F%A5?as=u&utm_source=inproduct 四、Edge浏览器 查看设置 - 查看高级设置 (灰色按钮) ,找到通知进行设置 注意:所有ie浏览器不支持桌面通知,这是个大杯具 兼容性 另外,qq浏览器也可以支持,其他的就不知道了 这里摘下别人的东西 浏览器支持情况:
SQL SERVER内存按存放数据的类型,大概可以分为三类: 1、buffer pool,存放数据页面的缓冲区,sql server数据都是存放在一个个8K的页面里,当用户需要使用这个页面上的数据时,都是把整个页面加载到内存的buffer pool区缓存起 来。 2、各类consumer: connect:SQL SERVER为每一个客户端连接分配一块内存,用来存储连接的信息,以及发过来的指令和缓存指令结果待待客户端取走 无数据:表、存储过程、索引等的元数据 锁:SQL SERVER中锁是稀有资源,会占用大量内存 Query plan:缓存SQL的执行计划 Optimizer:生成执行 计划过程中需要使用内存 3、线程内存:sql server会为每个线程分配0.5M的内存,用来存放线程的数据结构和相关信息 sql1: -- 查询SqlServer总体的内存使用情况 select type , sum(virtual_memory_reserved_kb) VM_Reserved , sum(virtual_memory_committed_kb) VM_Commited , sum(awe_allocated_kb) AWE_Allocated , sum(shared_memory_reserved_kb) Shared_Reserved , sum(shared_memory_committed_kb) Shared_Commited --, sum(single_pages_kb) --SQL2005、2008 --, sum(multi_pages_kb) --SQL2005、2008 from sys.dm_os_memory_clerks group by type order by type -- 查询当前数据库缓存的所有数据页面,哪些数据表,缓存的数据页面数量 -- 从这些信息可以看出,系统经常要访问的都是哪些表,有多大? select p.object_id, object_name=object_name(p.object_id), p.index_id, buffer_pages=count(*) from sys.allocation_units a, sys.dm_os_buffer_descriptors b, sys.partitions p where a.allocation_unit_id=b.allocation_unit_id and a.container_id=p.hobt_id and b.database_id=db_id() group by p.object_id,p.index_id order by buffer_pages desc -- 查询缓存的各类执行计划,及分别占了多少内存 -- 可以对比动态查询与参数化SQL(预定义语句)的缓存量 select cacheobjtype , objtype , sum(cast(size_in_bytes as bigint))/1024 as size_in_kb , count(bucketid) as cache_count from sys.dm_exec_cached_plans group by cacheobjtype, objtype order by cacheobjtype, objtype -- 查询缓存中具体的执行计划,及对应的SQL -- 将此结果按照数据表或SQL进行统计,可以作为基线,调整索引时考虑 -- 查询结果会很大,注意将结果集输出到表或文件中 SELECT usecounts , refcounts , size_in_bytes , cacheobjtype , objtype , TEXT FROM sys.dm_exec_cached_plans cp CROSS APPLY sys.dm_exec_sql_text(plan_handle) ORDER BY objtype DESC ; GO sql2: select OBJECT_NAME(object_id) 表名,COUNT(*) 页数,COUNT(*)*8/1024.0 Mb from sys.dm_os_buffer_descriptors a,sys.allocation_units b,sys.partitions c where a.allocation_unit_id=b.allocation_unit_id and b.container_id=c.hobt_id and database_id=DB_ID() group by OBJECT_NAME(object_id) order by 2 desc 参考: http://www.cnblogs.com/zhaoguan_wang/p/4602866.html http://blog.csdn.net/shutao917/article/details/51444424 http://blog.csdn.net/burgess_liu/article/details/52813727 http://blog.csdn.net/burgess_liu/article/details/17739725 http://blog.csdn.net/burgess_liu/article/details/17733149 sql server 各种等待类型-转
本文主要介绍扫普通链接二维码打开小程序, 详情请看官方文档https://mp.weixin.qq.com/debug/wxadoc/introduction/qrcode.html 配置普通链接二维码规则 生成二维码 访问https://cli.im/url,将https://test.com/linkcode?id=1_2生成二维码图片 小程序接收参数 if(option.q){ console.log(option.q); var link = decodeURIComponent(option.q); console.log(link); var paramArr = link.split('='); if (paramArr.length == 2){ var params = paramArr[1].split('_'); console.log(params[0]); console.log(params[1]); } } 这里使用1_2是指用来接受多参数,然后手动处理得到 欢迎阅读本系列文章:微信小程序开发教程目录
本文主要讲解如何使用本地接口进行开发,很多人都会遇到这个问题,特别是小程序上线后。 一、解决思路 在小程序开发工具设置网络代理,然后再通过Charles设置代理,将https域名转为本地接口进行访问。 以下示例的环境为win7 + 老版本的微信开发工具 二、准备工作 1.配置https域名 为小程序配置request合法域名,在登录公众号平台去设置。 2.安装Charles 下载地址:https://www.charlesproxy.com/download/ 三、配置Charles 1.安装根证书 首先,打开Charles,Help->SSL Proxying->Install Charles Root Certificate 安装证书到本地,指定位置到受信任的根证书颁发机构,否则需要进行下一步操作。 然后,按住win+r, 在输入运行窗口输入certmgr.msc回车,在证书界面找到 中级证书颁发机构=》证书 找到证书 “Chambers of Commerce Root - 2008”,然后拖到 受信任的根证书颁发机构=》证书,在弹出框点击 “是”。 2.映射https域名到本地访问地址 打开 Tools->Map Remote 添加线上域名于本地(开发环境)服务的映射 3.代理设置 打开 Proxy->Proxy Settings,该端口号后面会用到 4.SSL代理设置(重要) 打开 Proxy->SSL Proxying Settings, 没有设置此步骤,将会出现SSL Proxying not enabled for this host: enable in Proxy Settings, SSL locations的错误 以下例子中host为*,即过滤所有https,实际操作时指定为您的https地址即可 5.打开 Proxy->Windows Proxy 启用本地的代理服务 四、配置开发工具 1.选择手动设置代理,然后填写本地的IP,以及前边在Charles中设置的代理端口号 在开发工具的最左侧菜单的 设置 进入,如下设置 到此成功了 以往的坑 在微信开发者工具中,可以临时开启 开发环境不校验请求域名、TLS版本及HTTPS证书 选项,跳过服务器域名的校验。=》在不填appId时创建的项目下可以调试本地接口,但是微信接口都调用不了 参考: http://www.cnblogs.com/jiasm/archive/2016/11/14/6063317.html https://github.com/nighthary/someutil/blob/master/doc/charles%E6%8A%93%E5%8C%85https%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md 欢迎阅读本系列文章:微信小程序开发教程目录
一、partial 它是一个关键字修饰符。可以将类或结构、接口或方法的定义拆分到两个或更多个源文件中。 每个源文件包含类型或方法定义的一部分,编译应用程序时将把所有部分组合起来。修饰符不可用于委托或枚举声明中。 二、分部类 在以下几种情况下需要拆分类定义: 处理大型项目时,使一个类分布于多个独立文件中可以让多位程序员同时对该类进行处理。 使用自动生成的源时,无需重新创建源文件便可将代码添加到类中。 Visual Studio 在创建 Windows 窗体、Web 服务包装器代码等时都使用此方法。 无需修改 Visual Studio 创建的文件,就可创建使用这些类的代码。 简单示例 public partial class Test { public int Id { get; set; } public string Name { get; set; } public void ShowAge() { Console.WriteLine($" My age is {this.Age}"); } } public partial class Test { public string Age { get; set; } public void ShowNewAge() { ChangeAge(); Console.WriteLine($" My new age is {this.Age}"); } private void ChangeAge() { this.Age = "20"; } } class Program { static void Main(string[] args) { var test = new Test() { Id = 1, Name = "1", Age = "11" }; test.ShowAge(); test.ShowNewAge(); var info = $" name:{test.Name},age:{test.Age}"; Console.WriteLine(info); } } 编译时会对分部类型定义的属性进行合并;将从所有分部类型定义中对以下内容进行合并:XML 注释、接口、泛型类型参数属性、class 特性、成员 public class TestBase { } public interface ITest { void ShowAge(); } [SerializableAttribute] public partial class Test: TestBase { public int Id { get; set; } public string Name { get; set; } public void ShowAge() { Console.WriteLine($" My age is {this.Age}"); } } [ObsoleteAttribute] public partial class Test: ITest { public string Age { get; set; } public void ShowNewAge() { ChangeAge(); Console.WriteLine($" My new age is {this.Age}"); } private void ChangeAge() { this.Age = "20"; } } 它们等效于: [SerializableAttribute] [ObsoleteAttribute] public class Test : TestBase,ITest { //... } 如果将任意部分声明为抽象的,则整个类型都被视为抽象的。 如果将任意部分声明为密封的,则整个类型都被视为密封的。 //抽象类示例 public partial class PTest {} public abstract partial class PTest { } class Program { static void Main(string[] args) { //PTest PTest = new PTest();//提示:无法创建抽象类或接口"PTest"的实例 } } //密封类示例 public partial class STest { } public sealed partial class STest { } //public class SubTest: STest { } 三、分部方法 分部类或结构可以包含分部方法。 类的一个部分包含方法的签名。 分部方法声明由两个部分组成:定义和实现。 但是它的限制很多,只能在部分场景下使用。 分部方法声明必须以上下文关键字 partial 开头,并且方法必须返回 void。 分部方法可以有 ref 参数,但不能有 out 参数。 分部方法为隐式 private 方法,因此不能为 virtual 方法。 分部方法不能为 extern 方法,因为主体的存在确定了方法是在定义还是在实现。 分部方法可以有 static 和 unsafe 修饰符。 分部方法可以是泛型的。 约束将放在定义分部方法声明上,但也可以选择重复放在实现声明上。 参数和类型参数名称在实现声明和定义声明中不必相同。 你可以为已定义并实现的分部方法生成委托,但不能为已经定义但未实现的分部方法生成委托。 参考: https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods http://www.cnblogs.com/OpenCoder/archive/2009/10/27/1590328.html
第一章 类型基础 1 值类型与引用类型 CLR 支持两种类型:值类型和引用类型, C#的所有值类型均隐式派生自System.ValueType: 结构体:struct(直接派生于System.ValueType); 数值类型: 整 型:sbyte(System.SByte的别名),short(System.Int16),int(System.Int32),long (System.Int64),byte(System.Byte),ushort(System.UInt16),uint (System.UInt32),ulong(System.UInt64),char(System.Char); 浮点型:float(System.Single),double(System.Double); 用于财务计算的高精度decimal型:decimal(System.Decimal)。 bool型:bool(System.Boolean的别名); 用户定义的结构体(派生于System.ValueType)。 枚举:enum(派生于System.Enum); 可空类型(派生于System.Nullable<T>泛型结构体,T?实际上是System.Nullable<T>的别名)。 值类型(Value Type),值类型实例通常分配在线程的堆栈(stack)上,并且不包含任何指向实例数据的指针,因为变量本身就包含了其实例数据 C#有以下一些引用类型: 数组(派生于System.Array) 用户用定义的以下类型: 类:class(派生于System.Object); 接口:interface(接口不是一个“东西”,所以不存在派生于何处的问题。Anders在《C# Programming Language》中说,接口只是表示一种约定[contract]); 委托:delegate(派生于System.Delegate)。 object(System.Object的别名); 字符串:string(System.String的别名)。 可以看出: 引用类型与值类型相同的是,结构体也可以实现接口; 引用类型可以派生出新的类型,而值类型不能; 引用类型可以包含null值,值类型不能(可空类型功能允许将 null 赋给值类型); 引用类型变量的赋值只复制对对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值 简单说明:在内存中,变量会被分配在栈上来进行操作。值类型存储在栈上,而引用类型存储在堆上,栈上的变量记录该对象的地址 装箱和拆箱 装箱:将一个值类型转换成等价的引用类型。 拆箱:将一个已经装箱的引用类型转为值类型。 2 对象判等 未完待续... 参考: http://www.cnblogs.com/siqing99/archive/2012/04/03/2430918.html
一、定义 定义:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。对请求排队或记录请求日志,以及支持可撤消的操作。 主要解决:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。 何时使用:在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将"行为请求者"与"行为实现者"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。 如何解决:通过调用者调用接受者执行命令,顺序:调用者→接受者→命令。 二、结构 从命令模式的结构图可以看出,它涉及到五个角色,它们分别是: 客户角色:发出一个具体的命令并确定其接受者。 命令角色:声明了一个给所有具体命令类实现的抽象接口 具体命令角色:定义了一个接受者和行为的弱耦合,负责调用接受者的相应方法。 请求者角色:负责调用命令对象执行命令。 接受者角色:负责具体行为的执行。 三、适用场景 认为是命令的地方都可以使用命令模式,比如: 1、GUI 中每一个按钮都是一条命令。 2、模拟 CMD。 四、优缺点 优点: 1、降低了系统耦合度。 2、新的命令可以很容易添加到系统中去。 缺点:使用命令模式可能会导致某些系统有过多的具体命令类。 五、实现 /// <summary> /// 电视机 /// </summary> public class Television { /// <summary> /// 模式是关闭的 /// </summary> bool isopen = false; /// <summary> /// 切换关闭、打开 /// </summary> public string Switch() { if (isopen) { isopen = false; return "关闭电视机"; } else { isopen = true; return "打开电视机"; } } } /// <summary> /// 命令接口 /// </summary> public interface CommandInterface { /// <summary> /// 执行命令 /// </summary> string Execute(); } /// <summary> /// 电视机命令对象 /// </summary> public class TeleisionCommand : CommandInterface { /// <summary> /// 电视机对象 /// </summary> private Television Tv = new Television(); #region CommandInterface 成员 /// <summary> /// 执行命令 /// </summary> public string Execute() { return Tv.Switch(); } #endregion } /// <summary> /// 控制中心 /// </summary> public static class ControlContent { private static CommandInterface Command; public static void SetControlObjectCommand(CommandInterface cobject) { Command = cobject; } public static string ExecuteControlObject() { return Command.Execute(); } } class Program { static void Main(string[] args) { TeleisionCommand tvcommand = new TeleisionCommand(); ControlContent.SetControlObjectCommand(tvcommand); Console.WriteLine(ControlContent.ExecuteControlObject()); Console.WriteLine(ControlContent.ExecuteControlObject()); Console.ReadLine(); } } 参考 http://wangqingpei557.blog.51cto.com/1009349/626464/ http://www.runoob.com/design-pattern/command-pattern.html 欢迎阅读本系列文章:Head First设计模式之目录
一、定义 避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。 主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。 何时使用:在处理消息的时候以过滤很多道。 如何解决:拦截的类都实现统一接口。 二、结构 从责任链模式的定义可以发现,责任链模式涉及的对象只有处理者角色,但由于有多个处理者,它们具有共同的处理请求的方法,所以这里抽象出一个抽象处理者角色进行代码复用。这样分析下来,责任链模式的结构图也就不言而喻了,具体结构图如下所示。 主要涉及两个角色: 抽象处理者角色(Handler):定义出一个处理请求的接口。这个接口通常由接口或抽象类来实现。 具体处理者角色(ConcreteHandler):具体处理者接受到请求后,可以选择将该请求处理掉,或者将请求传给下一个处理者。因此,每个具体处理者需要保存下一个处理者的引用,以便把请求传递下去。 三、适用场景 1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3、可动态指定一组对象处理请求。 四、优缺点 优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。 缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。 五、实现 以公司采购东西为例来实现责任链模式。公司规定,采购架构总价在1万之内,经理级别的人批准即可,总价大于1万小于2万5的则还需要副总进行批准,总价大于2万5小于10万的需要还需要总经理批准,而大于总价大于10万的则需要组织一个会议进行讨论。 // 采购请求 public class PurchaseRequest { // 金额 public double Amount { get; set; } // 产品名字 public string ProductName { get; set; } public PurchaseRequest(double amount, string productName) { Amount = amount; ProductName = productName; } } // 审批人,Handler public abstract class Approver { public Approver NextApprover { get; set; } public string Name { get; set; } public Approver(string name) { this.Name = name; } public abstract void ProcessRequest(PurchaseRequest request); } // ConcreteHandler public class Manager : Approver { public Manager(string name) : base(name) { } public override void ProcessRequest(PurchaseRequest request) { if (request.Amount < 10000.0) { Console.WriteLine("{0} 批准购买 {1}", Name, request.ProductName); } else if (NextApprover != null) { NextApprover.ProcessRequest(request); } } } // ConcreteHandler,副总 public class VicePresident : Approver { public VicePresident(string name) : base(name) { } public override void ProcessRequest(PurchaseRequest request) { if (request.Amount < 25000.0) { Console.WriteLine("{0} 批准购买 {1}", Name, request.ProductName); } else if (NextApprover != null) { NextApprover.ProcessRequest(request); } } } // ConcreteHandler,总经理 public class President : Approver { public President(string name) : base(name) { } public override void ProcessRequest(PurchaseRequest request) { if (request.Amount < 100000.0) { Console.WriteLine("{0} 批准购买 {1}", Name, request.ProductName); } else { Console.WriteLine("Request需要组织一个会议讨论"); } } } class Program { static void Main(string[] args) { PurchaseRequest requestTelphone = new PurchaseRequest(4000.0, "手机"); PurchaseRequest requestSoftware = new PurchaseRequest(10000.0, "空调"); PurchaseRequest requestComputers = new PurchaseRequest(40000.0, "笔记本电脑"); Approver manager = new Manager("经理"); Approver Vp = new VicePresident("副总"); Approver Pre = new President("总经理"); // 设置责任链 manager.NextApprover = Vp; Vp.NextApprover = Pre; // 处理请求 manager.ProcessRequest(requestTelphone); manager.ProcessRequest(requestSoftware); manager.ProcessRequest(requestComputers); Console.ReadLine(); } } 结果 经理 批准购买 手机 副总 批准购买 空调 总经理 批准购买 笔记本电脑 参考: http://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html http://www.cnblogs.com/zhili/p/ChainOfResponsibity.html 欢迎阅读本系列文章:Head First设计模式之目录
一、定义 给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。 主要解决:对于一些固定文法构建一个解释句子的解释器。 何时使用:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。 如何解决:构件语法树,定义终结符与非终结符。 二、结构 组成: AbstractExpression(抽象表达式):定义解释器的接口,约定解释器的解释操作。 TerminalExpression(终结符表达式):用来实现语法规则中和终结符相关的操作,不再包含其它的解释器,如果用组合模式来构建抽象语法树的话,就相当于组合模式中的叶子对象,可以有多种终结符解释器。 NonterminalExpression(非终结符表达式):用来实现语法规则中非终结符相关的操作,通常一个解释器对应一个语法规则,可以包含其它的解释器,如果用组合模式来构建抽象语法树的话,就相当于组合模式中的组合对象,可以有多种非终结符解释器。 Context(上下文):它包含了解释器之外一些其他的全局信息;通常包含各个解释器需要的数据,或是公共的功能。 Client(客户端):指的是使用解释器的客户端,通常在这里去把按照语言的语法做的表达式,转换成为使用解释器对象描述的抽象语法树,然后调用解释操作。 三、适用场景 1、可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。 2、一些重复出现的问题可以用一种简单的语言来进行表达。 3、一个简单语法需要解释的场景。 四、优缺点 优点: 1、可扩展性比较好,灵活。 2、增加了新的解释表达式的方式。 3、易于实现简单文法。 缺点: 1、可利用场景比较少。 2、对于复杂的文法比较难维护。 3、解释器模式会引起类膨胀。 4、解释器模式采用递归调用方法。 五、实现 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DesignPatterns.Interpreter { class Program { static void Main(string[] args) { Console.WriteLine(ReplyClient.ApplyContent("Y0001")); Console.WriteLine(ReplyClient.ApplyContent("y0002")); Console.WriteLine(ReplyClient.ApplyContent("N0003")); Console.WriteLine(ReplyClient.ApplyContent("n0004")); } } /// <summary> /// 回复内容 /// </summary> public class ReplyContent { private string _ReplyText; public string ReplyText { get { return _ReplyText; } set { _ReplyText = value; } } } public abstract class InterPreter { public string ConvertContent(ReplyContent content) { if (content.ReplyText.Length == 0) return "请按规则回复审批短信."; return Excute(content.ReplyText); } public abstract string Excute(string key); } public class Approve : InterPreter { public override string Excute(string key) { if (key == "Y" || key == "y") { return "同意"; } else if (key == "N" || key == "n") { return "拒绝"; } else { return "回复内容有误,请重新回复."; } } } public class DocumentNum : InterPreter { public Dictionary<string, string> OddNum { get { Dictionary<string, string> OddID = new Dictionary<string, string>(); OddID.Add("0001", "123890890892345"); OddID.Add("0002", "123456717012345"); OddID.Add("0003", "123456669012345"); OddID.Add("0004", "123423444012345"); OddID.Add("0005", "123467845345345"); OddID.Add("0006", "123231234564345"); OddID.Add("0007", "128797897867745"); return OddID; } } public override string Excute(string key) { string value = null; if (OddNum.TryGetValue(key, out value)) { return value; } else { return "没找到对应的单号."; } } } public class ReplyClient { public static string ApplyContent(string replayValue) { string result = string.Empty; string approvevalue = replayValue.Substring(0, 1); string oddIDvalue = replayValue.Substring(1, 4); ReplyContent content = new ReplyContent(); content.ReplyText = approvevalue; InterPreter expression = new Approve(); result = string.Format("你{0}", expression.ConvertContent(content)); expression = new DocumentNum(); content.ReplyText = oddIDvalue; result += string.Format("单号是{0}的申请.\n", expression.ConvertContent(content)); return result; } } } 参考 http://www.runoob.com/design-pattern/interpreter-pattern.html http://www.cnblogs.com/JsonShare/p/7367535.html http://www.cnblogs.com/springyangwc/archive/2011/05/05/2037146.html 欢迎阅读本系列文章:Head First设计模式之目录
一、定义 提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示; 主要解决:不同的方式来遍历整个整合对象。 何时使用:遍历一个聚合对象。 如何解决:把在元素之间游走的责任交给迭代器,而不是聚合对象。 二、结构 组成: 抽象容器:一般是一个接口,提供一个iterator()方法,例如java中的Collection接口,List接口,Set接口等。 具体容器:就是抽象容器的具体实现类,比如List接口的有序列表实现ArrayList,List接口的链表实现LinkList,Set接口的哈希列表的实现HashSet等。 抽象迭代器:定义遍历元素所需要的方法,一般来说会有这么三个方法:取得第一个元素的方法first(),取得下一个元素的方法next(),判断是否遍历结束的方法hasNext(),移出当前对象的方法remove(), 迭代器实现:实现迭代器接口中定义的方法,完成集合的迭代。 三、适用场景 1、访问一个聚合对象的内容而无须暴露它的内部表示。 2、需要为聚合对象提供多种遍历方式。 3、为遍历不同的聚合结构提供一个统一的接口。 四、优缺点 优点: 1、它支持以不同的方式遍历一个聚合对象。 2、迭代器简化了聚合类。 3、在同一个聚合上可以有多个遍历。 4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。 5、系统需要访问一个聚合对象的内容而无需暴露它的内部表示。 缺点: 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。 迭代器模式在遍历的同时更改迭代器所在的集合结构会导致出现异常。所以使用foreach语句只能在对集合进行遍历,不能在遍历的同时更改集合中的元素。 五、实现 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DesignPatterns.Iterator { // 抽象聚合类 public interface IListCollection { Iterator GetIterator(); } // 迭代器抽象类 public interface Iterator { bool MoveNext(); Object GetCurrent(); void Next(); void Reset(); } // 具体聚合类 public class ConcreteList : IListCollection { int[] collection; public ConcreteList() { collection = new int[] { 2, 4, 6, 8 }; } public Iterator GetIterator() { return new ConcreteIterator(this); } public int Length { get { return collection.Length; } } public int GetElement(int index) { return collection[index]; } } // 具体迭代器类 public class ConcreteIterator : Iterator { // 迭代器要集合对象进行遍历操作,自然就需要引用集合对象 private ConcreteList _list; private int _index; public ConcreteIterator(ConcreteList list) { _list = list; _index = 0; } public bool MoveNext() { if (_index < _list.Length) { return true; } return false; } public object GetCurrent() { return _list.GetElement(_index); } public void Reset() { _index = 0; } public void Next() { if (_index < _list.Length) { _index++; } } } // 客户端 class Program { static void Main(string[] args) { Iterator iterator; IListCollection list = new ConcreteList(); iterator = list.GetIterator(); while (iterator.MoveNext()) { int i = (int)iterator.GetCurrent(); Console.WriteLine(i.ToString()); iterator.Next(); } Console.Read(); } } } 参考 http://www.cnblogs.com/zhili/p/IteratorPattern.html 欢迎阅读本系列文章:Head First设计模式之目录
一、定义 又称为调停者模式,定义一个中介对象来封装系列对象之间的交互。中介者使各个对象不需要显示地相互引用,从而使其耦合性松散,而且可以独立地改变他们之间的交互。 二、结构 组成: ● 抽象中介者(Mediator)角色:定义统一的接口用于各同事角色之间的通信,其中主要方法是一个(或多个)事件方法。 ● 具体中介者(ConcreteMediator)角色:实现了抽象中介者所声明的事件方法。具体中介者知晓所有的具体同事类,并负责具体的协调各同事对象的交互关系。 ● 抽象同事类(Colleague)角色:定义出中介者到同事角色的接口。同事角色只知道中介者而不知道其余的同事角色。与其他的同事角色通信的时候,一定要通过中介者角色协作。 ● 具体同事类(ConcreteColleague)角色:所有的具体同事类均从抽象同事类继承而来。实现自己的业务,在需要与其他同事通信的时候,就与持有的中介者通信,中介者会负责与其他的同事交互。 代码结构 //抽象中介者类 public interface IMediator { /** * 同事对象在自身改变的时候来通知中介者的方法 * 让中介者去负责相应的与其他同事对象的交互 */ void Changed(Colleague c); } //抽象同事类 public abstract class Colleague { //持有一个中介者对象 private IMediator _mediator; /** * 构造函数 */ public Colleague(IMediator mediator) { this._mediator = mediator; } /** * 获取当前同事类对应的中介者对象 */ public IMediator GetMediator() { return _mediator; } } //具体中介者类 public class ConcreteMediator : IMediator { //持有并维护同事A private ConcreteColleagueA _colleagueA; //持有并维护同事B private ConcreteColleagueB _colleagueB; public void SetColleagueA(ConcreteColleagueA colleagueA) { this._colleagueA = colleagueA; } public void SetColleagueB(ConcreteColleagueB colleagueB) { this._colleagueB = colleagueB; } public void Changed(Colleague c) { /** * 某一个同事类发生了变化,通常需要与其他同事交互 * 具体协调相应的同事对象来实现协作行为 */ } } //具体同事类 public class ConcreteColleagueA : Colleague { public ConcreteColleagueA(IMediator mediator) : base(mediator) { } /** * 示意方法,执行某些操作 */ public void Operation() { //在需要跟其他同事通信的时候,通知中介者对象 GetMediator().Changed(this); } } public class ConcreteColleagueB : Colleague { public ConcreteColleagueB(IMediator mediator) : base(mediator) { } /** * 示意方法,执行某些操作 */ public void Operation() { //在需要跟其他同事通信的时候,通知中介者对象 GetMediator().Changed(this); } } 三、适用场景 1、系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。 2、想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。 应用实例 1、中国加入 WTO 之前是各个国家相互贸易,结构复杂,现在是各个国家通过 WTO 来互相贸易。 2、机场调度系统。 3、MVC 框架,其中C(控制器)就是 M(模型)和 V(视图)的中介者。 四、优缺点 优点: 1、降低了类的复杂度,将一对多转化成了一对一。 2、各个类之间的解耦。 3、符合迪米特原则。 缺点:中介者会庞大,变得复杂难以维护。 五、实现 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DesignPatterns.Mediator { class Program { static void Main(string[] args) { MessageMediator mediator = new MessageMediator(); ConcreteColleagueA a = new ConcreteColleagueA(mediator, "A"); ConcreteColleagueB b = new ConcreteColleagueB(mediator, "B"); ConcreteColleagueC c = new ConcreteColleagueC(mediator, "C"); mediator.SetColleagueA(a); mediator.SetColleagueB(b); mediator.SetColleagueC(c); mediator.Chat(a, "中午吃啥饭?"); Console.WriteLine("===="); mediator.Chat(b, "我想吃刀削面"); Console.WriteLine("===="); mediator.Chat(c, "我也想吃刀削面"); Console.WriteLine("===="); mediator.Chat(a, "行,那中午一起去吃刀削面吧"); } } //抽象中介者类 public interface IMediator { void Chat(Colleague c, string message); } //抽象同事类 public abstract class Colleague { private string name; //持有一个中介者对象 private IMediator _mediator; public Colleague(IMediator mediator, string name) { this._mediator = mediator; this.name = name; } /** * 获取当前同事类对应的中介者对象 */ public IMediator GetMediator() { return _mediator; } /** * 获取昵称 */ public string getName() { return name; } /** * 接收消息 */ public abstract void receive(string message); /** * 发送消息 */ public abstract void send(string message); } //具体中介者类 public class MessageMediator : IMediator { //持有并维护同事A private ConcreteColleagueA _colleagueA; //持有并维护同事B private ConcreteColleagueB _colleagueB; private ConcreteColleagueC _colleagueC; public void SetColleagueA(ConcreteColleagueA colleagueA) { this._colleagueA = colleagueA; } public void SetColleagueB(ConcreteColleagueB colleagueB) { this._colleagueB = colleagueB; } public void SetColleagueC(ConcreteColleagueC colleagueC) { this._colleagueC = colleagueC; } public void Chat(Colleague c, string message) { if (_colleagueA != null) { _colleagueA.send(message); _colleagueB.receive(message); _colleagueC.receive(message); } else if (_colleagueB != null) { _colleagueB.send(message); _colleagueA.receive(message); _colleagueC.receive(message); } else if (_colleagueC != null) { _colleagueC.send(message); _colleagueA.receive(message); _colleagueB.receive(message); } } } //具体同事类 public class ConcreteColleagueA : Colleague { public ConcreteColleagueA(IMediator mediator, string name) : base(mediator, name) { } /** * 示意方法,执行某些操作 */ public void Operation(string message) { //在需要跟其他同事通信的时候,通知中介者对象 GetMediator().Chat(this, message); } public override void receive(String message) { Console.WriteLine("【A】收到消息:【" + message + "】"); } public override void send(String message) { Console.WriteLine("【A】发出消息:【" + message + "】"); } } public class ConcreteColleagueB : Colleague { public ConcreteColleagueB(IMediator mediator, string name) : base(mediator, name) { } /** * 示意方法,执行某些操作 */ public void Operation(string message) { //在需要跟其他同事通信的时候,通知中介者对象 GetMediator().Chat(this, message); } public override void receive(String message) { Console.WriteLine("【B】收到消息:【" + message + "】"); } public override void send(String message) { Console.WriteLine("【B】发出消息:【" + message + "】"); } } public class ConcreteColleagueC : Colleague { public ConcreteColleagueC(IMediator mediator, string name) : base(mediator, name) { } /** * 示意方法,执行某些操作 */ public void Operation(string message) { //在需要跟其他同事通信的时候,通知中介者对象 GetMediator().Chat(this, message); } public override void receive(String message) { Console.WriteLine("【C】收到消息:【" + message + "】"); } public override void send(String message) { Console.WriteLine("【C】发出消息:【" + message + "】"); } } } 结果 【A】发出消息:【中午吃啥饭?】 【B】收到消息:【中午吃啥饭?】 【C】收到消息:【中午吃啥饭?】 ==== 【A】发出消息:【我想吃刀削面】 【B】收到消息:【我想吃刀削面】 【C】收到消息:【我想吃刀削面】 ==== 【A】发出消息:【我也想吃刀削面】 【B】收到消息:【我也想吃刀削面】 【C】收到消息:【我也想吃刀削面】 ==== 【A】发出消息:【行,那中午一起去吃刀削面吧】 【B】收到消息:【行,那中午一起去吃刀削面吧】 【C】收到消息:【行,那中午一起去吃刀削面吧】 参考: http://www.cnblogs.com/JsonShare/p/7263876.html http://www.runoob.com/design-pattern/mediator-pattern.html 欢迎阅读本系列文章:Head First设计模式之目录
一、定义 不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样就可以将该对象恢复到原先保存的状态 二、结构 备忘录模式中主要有三类角色: 发起人角色:记录当前时刻的内部状态,负责创建和恢复备忘录数据。 备忘录角色:负责存储发起人对象的内部状态,在进行恢复时提供给发起人需要的状态。 管理者角色:负责保存备忘录对象,但是不能对备忘录对象的内容进行操作或检查。 三、适用场景 1、需要保存/恢复数据的相关状态场景。 2、提供一个可回滚的操作。 四、优缺点 优点: 1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。 2、实现了信息的封装,使得用户不需要关心状态的保存细节。 缺点: 消耗资源。 1、如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。 2、由于备份的信息是由发起人自己提供的,所以管理者无法预知备份的信息的大小,存在一定的未知风险。 五、实现 下面以备份手机通讯录为例子来实现了备忘录模式,具体的实现代码如下所示: 单次备份 // 联系人 public class ContactPerson { public string Name { get; set; } public string MobileNum { get; set; } } // 发起人 public class MobileOwner { // 发起人需要保存的内部状态 public List<ContactPerson> ContactPersons { get; set; } public MobileOwner(List<ContactPerson> persons) { ContactPersons = persons; } // 创建备忘录,将当期要保存的联系人列表导入到备忘录中 public ContactMemento CreateMemento() { // 这里也应该传递深拷贝,new List方式传递的是浅拷贝, // 因为ContactPerson类中都是string类型,所以这里new list方式对ContactPerson对象执行了深拷贝 // 如果ContactPerson包括非string的引用类型就会有问题,所以这里也应该用序列化传递深拷贝 return new ContactMemento(new List<ContactPerson>(this.ContactPersons)); } // 将备忘录中的数据备份导入到联系人列表中 public void RestoreMemento(ContactMemento memento) { // 下面这种方式是错误的,因为这样传递的是引用, // 则删除一次可以恢复,但恢复之后再删除的话就恢复不了. // 所以应该传递contactPersonBack的深拷贝,深拷贝可以使用序列化来完成 this.ContactPersons = memento.contactPersonBack; } public void Show() { Console.WriteLine("联系人列表中有{0}个人,他们是:", ContactPersons.Count); foreach (ContactPerson p in ContactPersons) { Console.WriteLine("姓名: {0} 号码为: {1}", p.Name, p.MobileNum); } } } // 备忘录 public class ContactMemento { // 保存发起人的内部状态 public List<ContactPerson> contactPersonBack; public ContactMemento(List<ContactPerson> persons) { contactPersonBack = persons; } } // 管理角色 public class Caretaker { public ContactMemento ContactM { get; set; } } class Program { static void Main(string[] args) { List<ContactPerson> persons = new List<ContactPerson>() { new ContactPerson() { Name= "Learning Hard", MobileNum = "123445"}, new ContactPerson() { Name = "Tony", MobileNum = "234565"}, new ContactPerson() { Name = "Jock", MobileNum = "231455"} }; MobileOwner mobileOwner = new MobileOwner(persons); mobileOwner.Show(); // 创建备忘录并保存备忘录对象 Caretaker caretaker = new Caretaker(); caretaker.ContactM = mobileOwner.CreateMemento(); // 更改发起人联系人列表 Console.WriteLine("----移除最后一个联系人--------"); mobileOwner.ContactPersons.RemoveAt(2); mobileOwner.Show(); // 恢复到原始状态 Console.WriteLine("-------恢复联系人列表------"); mobileOwner.RestoreMemento(caretaker.ContactM); mobileOwner.Show(); Console.Read(); } } 多次备份 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace DesignPatterns.Mememto { // 联系人 public class ContactPerson { public string Name { get; set; } public string MobileNum { get; set; } } // 发起人 public class MobileOwner { public List<ContactPerson> ContactPersons { get; set; } public MobileOwner(List<ContactPerson> persons) { ContactPersons = persons; } // 创建备忘录,将当期要保存的联系人列表导入到备忘录中 public ContactMemento CreateMemento() { // 这里也应该传递深拷贝,new List方式传递的是浅拷贝, // 因为ContactPerson类中都是string类型,所以这里new list方式对ContactPerson对象执行了深拷贝 // 如果ContactPerson包括非string的引用类型就会有问题,所以这里也应该用序列化传递深拷贝 return new ContactMemento(new List<ContactPerson>(this.ContactPersons)); } // 将备忘录中的数据备份导入到联系人列表中 public void RestoreMemento(ContactMemento memento) { if (memento != null) { // 下面这种方式是错误的,因为这样传递的是引用, // 则删除一次可以恢复,但恢复之后再删除的话就恢复不了. // 所以应该传递contactPersonBack的深拷贝,深拷贝可以使用序列化来完成 this.ContactPersons = memento.ContactPersonBack; } } public void Show() { Console.WriteLine("联系人列表中有{0}个人,他们是:", ContactPersons.Count); foreach (ContactPerson p in ContactPersons) { Console.WriteLine("姓名: {0} 号码为: {1}", p.Name, p.MobileNum); } } } // 备忘录 public class ContactMemento { public List<ContactPerson> ContactPersonBack { get; set; } public ContactMemento(List<ContactPerson> persons) { ContactPersonBack = persons; } } // 管理角色 public class Caretaker { // 使用多个备忘录来存储多个备份点 public Dictionary<string, ContactMemento> ContactMementoDic { get; set; } public Caretaker() { ContactMementoDic = new Dictionary<string, ContactMemento>(); } } class Program { static void Main(string[] args) { List<ContactPerson> persons = new List<ContactPerson>() { new ContactPerson() { Name= "Learning Hard", MobileNum = "123445"}, new ContactPerson() { Name = "Tony", MobileNum = "234565"}, new ContactPerson() { Name = "Jock", MobileNum = "231455"} }; MobileOwner mobileOwner = new MobileOwner(persons); mobileOwner.Show(); // 创建备忘录并保存备忘录对象 Caretaker caretaker = new Caretaker(); caretaker.ContactMementoDic.Add(DateTime.Now.ToString(), mobileOwner.CreateMemento()); // 更改发起人联系人列表 Console.WriteLine("----移除最后一个联系人--------"); mobileOwner.ContactPersons.RemoveAt(2); mobileOwner.Show(); // 创建第二个备份 Thread.Sleep(1000); caretaker.ContactMementoDic.Add(DateTime.Now.ToString(), mobileOwner.CreateMemento()); // 恢复到原始状态 Console.WriteLine("-------恢复联系人列表,请从以下列表选择恢复的日期------"); var keyCollection = caretaker.ContactMementoDic.Keys; foreach (string k in keyCollection) { Console.WriteLine("Key = {0}", k); } while (true) { Console.Write("请输入数字,按窗口的关闭键退出:"); int index = -1; try { index = Int32.Parse(Console.ReadLine()); } catch { Console.WriteLine("输入的格式错误"); continue; } ContactMemento contactMentor = null; if (index < keyCollection.Count && caretaker.ContactMementoDic.TryGetValue(keyCollection.ElementAt(index), out contactMentor)) { mobileOwner.RestoreMemento(contactMentor); mobileOwner.Show(); } else { Console.WriteLine("输入的索引大于集合长度!"); } } } } } 参考 http://www.cnblogs.com/JsonShare/p/7283972.html http://www.runoob.com/design-pattern/memento-pattern.html http://www.cnblogs.com/zhili/p/MementoPattern.html 欢迎阅读本系列文章:Head First设计模式之目录
一、定义 定义:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。 访问者模式适用于数据结构相对稳定的系统, 它把数据结构和作用于数据结构之上的操作之间的耦合度降低,使得操作集合可以相对自由地改变。 数据结构的每一个节点都可以接受一个访问者的调用,此节点向访问者对象传入节点对象,而访问者对象则反过来执行节点对象的操作。这样的过程叫做“双重分派”。节点调用访问者,将它自己传入,访问者则将某算法针对此节点执行。 二、结构图 这里需要明确一点:访问者模式中具体访问者的数目和具体节点的数目没有任何关系。从访问者的结构图可以看出,访问者模式涉及以下几类角色。 抽象访问者角色(Vistor):声明一个活多个访问操作,使得所有具体访问者必须实现的接口。 具体访问者角色(ConcreteVistor):实现抽象访问者角色中所有声明的接口。 抽象节点角色(Element):声明一个接受操作,接受一个访问者对象作为参数。 具体节点角色(ConcreteElement):实现抽象元素所规定的接受操作。 结构对象角色(ObjectStructure):节点的容器,可以包含多个不同类或接口的容器。 三、适用场景 1、对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。 2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。 四、优缺点 优点: 1、符合单一职责原则。 2、优秀的扩展性。 3、灵活性。 缺点: 1、具体元素对访问者公布细节,违反了迪米特原则。 2、具体元素变更比较困难。 3、违反了依赖倒置原则,依赖了具体类,没有依赖抽象。 五、实现 思路:商家定义了一个套餐A,为套餐A添加了土豆和花生,然后张三、李四才点了套餐A。 好处:不管多少人来,都是给他们的菜都是一样的 坏处:如果套餐A 突然加了一个菜 海带, 导致所有人都要来拿海带,所以变化是非常困难的。 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DesignPatterns.Visitor { class Program { static void Main(string[] args) { Food a = new Potato(); Food b = new Peanut(); var combo = new Combo(); combo.Add(a); combo.Add(b); Visitor charger = new Charger("张三"); Visitor workerOfPharmacy = new WorkerOfPharmacy("李四"); combo.Accpet(charger); Console.WriteLine(); combo.Accpet(workerOfPharmacy); } } /// <summary> /// 抽象访问者 /// </summary> public abstract class Visitor { protected string name { get; set; } public Visitor(string name) { this.name = name; } public abstract void visitor(Potato a); public abstract void visitor(Peanut b); } /// <summary> /// 具体访问者:张三 /// </summary> public class Charger : Visitor { public Charger(string name) : base(name) { } public override void visitor(Potato a) { Console.WriteLine("张三:" + this.name + " 取菜 " + a.GetName()); } public override void visitor(Peanut b) { Console.WriteLine("张三:" + this.name + " 取菜 " + b.GetName()); } } /// <summary> /// 具体访问者:李四 /// </summary> public class WorkerOfPharmacy : Visitor { public WorkerOfPharmacy(string name) : base(name) { } public override void visitor(Potato a) { Console.WriteLine("李四:" + this.name + " 取菜 " + a.GetName() ); } public override void visitor(Peanut b) { Console.WriteLine("李四:" + this.name + " 取菜 " + b.GetName()); } } /// <summary> /// 抽象元素:食物 /// </summary> public abstract class Food { protected string name { get; set; } public Food() { name = "我是食物"; } public string GetName() { return name; } public abstract void Accept(Visitor visitor); } /// <summary> /// 具体元素:土豆 /// </summary> public class Potato : Food { public Potato() { this.name = "土豆"; } public override void Accept(Visitor visitor) { visitor.visitor(this); } } /// <summary> /// 具体元素:花生 /// </summary> public class Peanut : Food { public Peanut() { this.name = "花生"; } public override void Accept(Visitor visitor) { visitor.visitor(this); } } /// <summary> /// 具体元素:套餐 /// </summary> public class Combo { private List<Food> list = new List<Food>(); public void Accpet(Visitor visitor) { foreach (var item in list) { item.Accept(visitor); } } public void Add(Food med) { list.Add(med); } public void Remove(Food med) { list.Remove(med); } } } 参考 http://blog.csdn.net/heyangyi_19940703/article/details/51374416 http://www.cnblogs.com/zhili/p/VistorPattern.html http://www.cnblogs.com/JsonShare/p/7380772.html 欢迎阅读本系列文章:Head First设计模式之目录
一、定义 定义:允许对象在内部状态改变时改变它的行为, 对象看起来好像修改了它的类。 主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。 何时使用:代码中包含大量与对象状态有关的条件语句。 如何解决:将各种具体的状态类抽象出来。 关键代码:通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if...else 等条件选择语句。 二、结构图 Context类:维护一个ConcreteState子类的一个实例,这个实例定义当前的状态。 State类:抽象状态类,定义一个接口以封装与Context的一个特定状态相关的行为。 ConcreteStateA,ConcreteStateB,ConcreteStateC类:具体状态类,每一个子类实现一个与Context的一个状态相关的行为。 三、适用场景 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。 四、优缺点 优点: 1、封装了转换规则。 2、枚举可能的状态,在枚举状态之前需要确定状态种类。 3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。 缺点: 1、状态模式的使用必然会增加系统类和对象的个数。 2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 3、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。 五、实现 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DesignPatterns.StatePattern { class Program { static void Main(string[] args) { // Open a new account Account account = new Account("Jim Johnson"); // Apply financial transactions account.Deposit(500.0); account.Deposit(300.0); account.Deposit(550.0); account.PayInterest(); account.Withdraw(2000.00); account.Withdraw(1100.00); // Wait for user Console.ReadKey(); } } class Account { private State _state; private string _owner; // Constructor public Account(string owner) { // New accounts are 'Silver' by default this._owner = owner; this._state = new SilverState(0.0, this); } // Properties public double Balance { get { return _state.Balance; } } public State State { get { return _state; } set { _state = value; } } public void Deposit(double amount) { _state.Deposit(amount); Console.WriteLine("Deposited {0:C} --- ", amount); Console.WriteLine("Balance = {0:C}", this.Balance); Console.WriteLine("Status = {0}",this.State.GetType().Name); Console.WriteLine(""); } public void Withdraw(double amount) { _state.Withdraw(amount); Console.WriteLine("Withdrew {0:C} --- ", amount); Console.WriteLine(" Balance = {0:C}", this.Balance); Console.WriteLine(" Status = {0}\n", this.State.GetType().Name); } public void PayInterest() { _state.PayInterest(); Console.WriteLine("Interest Paid --- "); Console.WriteLine(" Balance = {0:C}", this.Balance); Console.WriteLine(" Status = {0}\n", this.State.GetType().Name); } } /// <summary> /// The 'State' abstract class /// </summary> abstract class State { protected Account account; protected double balance; protected double interest; protected double lowerLimit; protected double upperLimit; // Properties public Account Account { get { return account; } set { account = value; } } public double Balance { get { return balance; } set { balance = value; } } public abstract void Deposit(double amount); public abstract void Withdraw(double amount); public abstract void PayInterest(); } /// <summary> /// A 'ConcreteState' class /// <remarks> /// Red indicates that account is overdrawn /// </remarks> /// </summary> class RedState : State { private double _serviceFee; // Constructor public RedState(State state) { this.balance = state.Balance; this.account = state.Account; Initialize(); } private void Initialize() { // Should come from a datasource interest = 0.0; lowerLimit = -100.0; upperLimit = 0.0; _serviceFee = 15.00; } public override void Deposit(double amount) { balance += amount; StateChangeCheck(); } public override void Withdraw(double amount) { amount = amount - _serviceFee; Console.WriteLine("No funds available for withdrawal!"); } public override void PayInterest() { // No interest is paid } private void StateChangeCheck() { if (balance > upperLimit) { account.State = new SilverState(this); } } } /// <summary> /// A 'ConcreteState' class /// <remarks> /// Silver indicates a non-interest bearing state /// </remarks> /// </summary> class SilverState : State { // Overloaded constructors public SilverState(State state) : this(state.Balance, state.Account) { } public SilverState(double balance, Account account) { this.balance = balance; this.account = account; Initialize(); } private void Initialize() { // Should come from a datasource interest = 0.0; lowerLimit = 0.0; upperLimit = 1000.0; } public override void Deposit(double amount) { balance += amount; StateChangeCheck(); } public override void Withdraw(double amount) { balance -= amount; StateChangeCheck(); } public override void PayInterest() { balance += interest * balance; StateChangeCheck(); } private void StateChangeCheck() { if (balance < lowerLimit) { account.State = new RedState(this); } else if (balance > upperLimit) { account.State = new GoldState(this); } } } /// <summary> /// A 'ConcreteState' class /// <remarks> /// Gold indicates an interest bearing state /// </remarks> /// </summary> class GoldState : State { // Overloaded constructors public GoldState(State state) : this(state.Balance, state.Account) { } public GoldState(double balance, Account account) { this.balance = balance; this.account = account; Initialize(); } private void Initialize() { // Should come from a database interest = 0.05; lowerLimit = 1000.0; upperLimit = 10000000.0; } public override void Deposit(double amount) { balance += amount; StateChangeCheck(); } public override void Withdraw(double amount) { balance -= amount; StateChangeCheck(); } public override void PayInterest() { balance += interest * balance; StateChangeCheck(); } private void StateChangeCheck() { if (balance < 0.0) { account.State = new RedState(this); } else if (balance < lowerLimit) { account.State = new SilverState(this); } } } } 现实中还有其他很多例子,比如自动贩卖机、电梯等。 状态模式的关键在于 状态变化时引起行为的变化,它是被动的触发 策略模式的差别在于,它是由外部(client)主动引起行为的变化,可以随意控制它想要执行的行为。 参考文章: http://www.cnblogs.com/ywqu/archive/2010/01/26/1656418.html http://www.runoob.com/design-pattern/state-pattern.html http://www.cnblogs.com/JsonShare/p/7246915.html 欢迎阅读本系列文章:Head First设计模式之目录
一、打印样式 区别显示和打印的样式 使用media="print"控制打印时的样式,如下: 打印时不显示打印按钮,设置页面宽度 <style media="print"> .toolbox { display: none; } .container { max-width: 210mm; } </style> <style> .container { margin: 0 auto; max-width: 960px; } <style> 使用独立的样式文件 <link rel="stylesheet" type="text/css" media="screen" href="mycss.css"> <link rel="stylesheet" type="text/css" media="print" href="myprintcss.css"> css里media的使用 我们在网页里引用外部的css文件时,通常是用如下的代码:<link rel="stylesheet" type="text/css" href="mycss.css"> 实际上,上面的link对象里,我们是省略了一个叫“media”的属性,这个属性指定样式表规则用于指定的设备类型。它有如下值可用:all-- 用于所有设备类型 aural-- 用于语音和音乐合成器 braille-- 用于触觉反馈设备 embossed-- 用于凸点字符(盲文)印刷设备 handheld-- 用于小型或手提设备 print-- 用于打印机 projection-- 用于投影图像,如幻灯片 screen-- 用于计算机显示器 tty-- 用于使用固定间距字符格的设备。如电传打字机和终端 tv-- 用于电视类设备 这么多的值,并不是每个都可用,因为浏览器厂商并没有全部实现它们。 二、分页打印 打印需要分页时,但是自动分页又无法满足自己的需求时,需要手动分页 <style type="text/css"> .pageBreak{ page-break-after:always;} .pageBreakBefore{ page-break-before:always;} </style> css里用于打印的属性 page-break-after : auto | always | avoid | left | right | null参数: auto : 假如需要在对象之后插入页分割符 always :始终在对象之后插入页分割符 avoid : 避免在对象后面插入页分割符 left : 在对象后面插入页分割符直到它到达一个空白的左页边 right :在对象后面插入页分割符直到它到达一个空白的右页边 null : 空值。IE5用来取消页分割符设置 这个page-break-after,主要用来在打印时插入一个分页符,分页就靠它了。它还有个双胞胎的兄弟,叫page-break-before,参数和它一样,看名字即知道它是用来在对象之前插入分页符。 示例如下: <!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html;charset=UTF-8" /> <title>Paginated HTML</title> <style type="text/css" media="print"> div.page { page-break-after: always; page-break-inside: avoid; } </style> </head> <body> <div class="page"> <h1>This is Page 1</h1> </div> <div class="page"> <h1>This is Page 2</h1> </div> <div class="page"> <h1>This is Page 3</h1> </div> </body> </html> 打印模板 关于.NET实现按模板分页的部分关键代码 样式 <link rel="stylesheet" href="@Url.Content("~/Content/normalize.css")"> <style media="print"> .toolbox { display: none; } .container { max-width: 210mm; } </style> <style> .container { margin: 0 auto; max-width: 960px; } .table-wrap { width: 100%; height: 903px; } table > * { font-size: 14px !important; } table { border-collapse: collapse; border: 1px solid black; width: 100%; } table tr th { height: 20px; } table tr td { border: 1px solid black; text-align: center; } table tr td.left { border: 1px solid black; text-align: left; padding-left: 5px; } .page-foot { margin-top: 10px; text-align: center; width: 100%; } .pageBreakBefore { page-break-before: always; -webkit-break-inside: avoid; page-break-inside: avoid; } .subbox { text-align: center; } .footbox { display: block; } .namebox { display: inline-block; width: 50%; margin-top: 10px; } .hospitalName { width: 200px; text-align: center; } .timeSpan { width: 200px; text-align: center; } .text_line { text-decoration: underline; } .toolbox { margin-top: 10px; text-align: right; } .wp10 { width: 10%; } .wp20 { width: 20%; } .wp50 { width: 50%; } </style> html <div class="toolbox"> <span>提醒:在非ie打印预览时,通过预览界面的“更多设置”去掉页面上的页眉页脚。 &emsp; </span> <button id="btnPrint">打印</button> <button onclick="closeWin()">关闭页面</button> </div> @for (int index = 0; index < Model.List.Count;) { <h2 style="text-align: center;"> @string.Format("{0}服务周报", Model.CompanyName) </h2> <p class="subbox"> <span class="hospitalName">甲方名称 <strong class="text_line">@Model.PartyACompanyName</strong> </span> <span class="timeSpan">日期 <strong class="text_line">@Model.StartDate</strong> 至 <strong class="text_line">@Model.EndDate</strong></span> </p> <div class="table-wrap"> <table> <tbody> <tr> <th class="wp10">序号</th> <th class="wp40">标题</th> <th class="wp20">姓名</th> </tr> @{ for (var j = 0; j < 40; j++) { if (index < Model.List.Count) { <tr> <td class="wp10">@(index + 1)</td> <td class="left wp40">@Model.List[index].Title</td> <td class="wp20">@Model.List[index].UserName</td> </tr> index++; } } } </tbody> </table> </div> <div class="footbox"> <div class="namebox"> <span class="hospitalName">甲方: ____________ </span> <span class="timeSpan"> 日期: ____________ </span> </div> <div class="namebox" style="text-align: right; width: 49%;"> <span class="hospitalName">乙方: ____________ </span> <span class="timeSpan"> 日期: ____________ </span> </div> <div class="page-foot">第@((index + 1) / 40 + (index % 40 > 0 ? 1 : 0))页</div> </div> <div class="pageBreakBefore"></div> } 脚本 <script src="~/Scripts/jquery-2.2.0.min.js"></script> <script> function pagesetup_null() { var hkey_root, hkey_path, hkey_key; hkey_root = "HKEY_CURRENT_USER"; hkey_path = "\\Software\\Microsoft\\Internet Explorer\\PageSetup\\"; try { var RegWsh = new ActiveXObject("WScript.Shell"); hkey_key = "header"; RegWsh.RegWrite(hkey_root + hkey_path + hkey_key, ""); hkey_key = "footer"; RegWsh.RegWrite(hkey_root + hkey_path + hkey_key, ""); } catch (e) { } } $(function () { $("#btnPrint").click(function () { var explorer = window.navigator.userAgent; if (explorer.indexOf("MSIE") >= 0) { pagesetup_null(); } window.print(); }); }); function closeWin() { window.open("", "_self").close(); } </script> 参考与分享: 分享几款免费的web打印控件 WEB打印系列教程之二--使用WScript.Shell通过编程方式进行复杂的WEB打印设置 WEB打印系列教程之三--简单的WEB打印分页设置 每页都有的表头和打印分页 WEB打印-网页打印功能(带分页、可多页打印) Google Chrome打印分页符 web打印中如何强制分页
Bootstrap学习系列教程 本系列教程是自己在工作中使用到而记录的,如有错误之处,请给与指正 第一章 Bootstrap简介 第二章 时间控件(DateTime Picker) 第三章 续:时间控件(TimePicker) 第四章 标签页 第五章 使用 Bootstrap Typeahead 组件(百度下拉效果) 第六章 使用 Bootstrap Typeahead 组件(百度下拉效果)(续) 第七章 模态框 第八章 让Bootstrap轮播插件carousel支持左右滑动手势的三种方法 第九章 BootstrapTable的使用 HTML5商城开发系列教程 HTML5商城开发一 楼层滚动加载数据 HTML5商城开发二 通过位移实现拖动效果 HTML5商城开发三 jquery 星星评分插件 HTML5商城开发四 多图或多商品的水平滚动展示 HTML5商城开发五 实现返回页面顶部 HTML5常用知识点 HTML5 history新特性pushState、replaceState HTML5 respond.js 解决IE6~8的响应式布局问题 html之marquee详解 HTML转义字符大全 HTML <map> 设置图热点
本系列教程是自己在工作中使用到而记录的,如有错误之处,请给与指正 文章目录 MVC4 开篇 第一章 初识MVC4 第二章 下山遇虎(@helper) 第三章 Models模块属性详解 第四章 在MVC4.0中对脚本以及样式表的引用变化 第五章 MVC之Bundle详解 第六章 MVC之 FileResult 和 JS请求二进制文件
一、定义 保证一个类仅有一个实例,并提供一个访问它的全局访问点。通过单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例。 Singleton模式中的实例构造器可以设置为protected以允许子类派生。 Singleton模式一般不要支持ICloneable接口,因为这可能会导致多个对象实例,与Singleton模式的初衷违背。 Singleton模式一般不要支持序列化,因为这也有可能导致多个对象实例,同样跟Singleton模式的初衷违背。 Singleton模式只考虑到了对象创建的管理,并没有考虑对象销毁的管理。就支持垃圾回收的平台和对象的开销来讲,我们一般没必要对其销毁进行特殊的管理。 二、结构 创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。 SingleObject 类提供了一个静态方法,供外界获取它的静态实例。 三、适用场景 1、要求生产唯一序列号。 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。 四、优缺点 优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。 2、避免对资源的多重占用(比如写文件操作)。 缺点: 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。 五、实现 单例模式的实现有多种方式 1、线程不安全 public sealed class Singleton { public int Count { get; set; } private static Singleton instance = null; public static Singleton Instance { get { if (instance == null) { instance = new Singleton(); } return instance; } } private Singleton() { Count = DateTime.Now.Millisecond; } } public class SimpleSingleton { private SimpleSingleton() { } public static readonly SimpleSingleton instance = new SimpleSingleton(); } 说明:SimpleSingleton是 Singleton 的简化版,它利用.NET的特性写的,其实和Singleton 一样 2.线程安全 /// <summary> /// 单例-线程安全 /// </summary> public sealed class SafeSingleton { public int Count { get; set; } private static SafeSingleton instance = null; private static readonly object safeLock = new object(); public static SafeSingleton Instance { get { if (instance == null) { lock (safeLock) { if (instance == null) { instance = new SafeSingleton(); } } } return instance; } } private SafeSingleton() { Count = DateTime.Now.Millisecond; } } 3.单例测试 class Program { static void Main(string[] args) { SayName(); SayNewName(); Thread.Sleep(1000); } private static async Task SayName() { await Task.Run(() => { var s = Singleton.Instance; Console.WriteLine("count:" + s.Count); var s1 = SafeSingleton.Instance; Console.WriteLine("【safe】 count:" + s1.Count); }); } private static async Task SayNewName() { await Task.Run(() => { var s = Singleton.Instance; Console.WriteLine("new count:" + s.Count); var s1 = SafeSingleton.Instance; Console.WriteLine("【safe】 new count:" + s1.Count); }); } } 结果 4、单例模式的复用 代码 /// <summary> /// 4、使用 C# 2.0 泛型来完成单例模式的重用 /// </summary> /// <typeparam name="T"></typeparam> public class SingletonProvider<T> where T : new() { SingletonProvider() { } public static T Instance { get { return SingletonCreator.instance; } } class SingletonCreator { static SingletonCreator() { } internal static readonly T instance = new T(); } } /// <summary> /// 4、业务类demo /// </summary> public class TestClass { private string _createdTimestamp; public TestClass() { _createdTimestamp = DateTime.Now.ToString(); } public void Write() { Console.WriteLine(_createdTimestamp); } } /// <summary> /// 4、范型单例示例 /// </summary> public class demo { private void dosomething() { SingletonProvider<TestClass>.Instance.Write(); } } 参考: http://www.runoob.com/design-pattern/singleton-pattern.html http://blog.csdn.net/dabian1987/article/details/6951652 http://blog.csdn.net/jiankunking/article/details/50867050 http://www.cnblogs.com/kmsfan/p/4562323.html http://www.cnblogs.com/dreign/archive/2012/05/08/2490212.html(推荐) 欢迎阅读本系列文章:Head First设计模式之目录
一、简述 Bundle,英文原意就是捆、收集、归拢。在MVC中的Bundle技术,也就是一个对css和js文件的捆绑压缩的技术。 它的用处: 将多个请求捆绑为一个请求,减少服务器请求数 压缩javascript,css等资源文件,减小网络带宽,提升性能 使用Bundle技术,并且拥有缓存功能,同时也可以对资源文件进行一定的模块化管理,可使用正则对需要的文件进行过滤匹配,也可以使用cdn文件 二、技术详解 1.怎么开启bundle 在项目的App_Start文件夹中,会有一个BundleConfig.cs文件; public class BundleConfig { // For more information on bundling, visit https://go.microsoft.com/fwlink/?LinkId=301862 public static void RegisterBundles(BundleCollection bundles) { bundles.Add(new ScriptBundle("~/bundles/jquery").Include( "~/Scripts/jquery-{version}.js")); bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( "~/Scripts/jquery.validate*")); // 使用要用于开发和学习的 Modernizr 的开发版本。然后,当你做好 // ready for production, use the build tool at https://modernizr.com to pick only the tests you need. bundles.Add(new ScriptBundle("~/bundles/modernizr").Include( "~/Scripts/modernizr-*")); bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include( "~/Scripts/bootstrap.js", "~/Scripts/respond.js")); bundles.Add(new StyleBundle("~/Content/css").Include( "~/Content/bootstrap.css", "~/Content/site.css")); BundleTable.EnableOptimizations = true; //是否打包压缩 } } 对Bundle的注册是在项目根下的Global.asax文件中,这个文件中的Application_Start是网站程序的开始,里面注册了网站各种初始化的内容,其中就包括对BundleTable的Bundle添加:BundleConfig.RegisterBundles(BundleTable.Bundles); 默认情况下,Bundle是会对js和css进行压缩打包的,不过有一个属性可以显式的说明是否需要打包压缩。 BundleTable.EnableOptimizations = true; 配置web.config,关掉调试状态,否则不会进行压缩。 <system.web> <compilation debug="false" targetFramework="4.5.2" /> </system.web> 2.如何使用 视图中调用方法: @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/bootstrap") 捆绑框架如以下几个共同的约定: 如果“FileX.min.js”和“FileX.js”都存在,请为发行版选择“.min”文件。 选择用于调试的非".min"版本。 忽略"-vsdoc"仅使用智能感知的文件 (如 jquery-1.7.1-vsdoc.js)。 如上所示的{version}通配符匹配用于自动创建一个 jQuery 束具有适当版本的 jQuery脚本文件夹中。在此示例中,使用通配符提供了以下好处: 允许您使用 NuGet 更新到新的 jQuery 版本而无需更改前面的绑定代码或 jQuery 引用在您查看网页。 自动选择完整版,为调试配置和发布的".min"版本生成。 3.使用 CDN 以下代码将使用 CDN jQuery 绑定来替换本地 jQuery 绑定。 public static void RegisterBundles(BundleCollection bundles) { bundles.UseCdn = true; //enable CDN support //add link to jquery on the CDN var jqueryCdnPath = "http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.min.js"; bundles.Add(new ScriptBundle("~/bundles/jquery",jqueryCdnPath).Include("~/Scripts/jquery-{version}.js")); } 在上面的代码中,jQuery 将请求从 CDN 虽然在释放模式和 jQuery 的调试版本将被回迁本地在调试模式下。当使用 CDN,你应该有一个回退机制在 CDN 请求失败的情况下。下面的标记片段从布局文件的末尾显示脚本添加请求 jQuery 应 CDN 失败。 @Scripts.Render("~/bundles/jquery") <script type="text/javascript"> if (typeof jQuery == 'undefined') { var e = document.createElement('script'); e.src = '@Url.Content("~/Scripts/jquery-1.7.1.js")'; e.type = 'text/javascript'; document.getElementsByTagName("head")[0].appendChild(e); } </script> @RenderSection("scripts", required: false) 三、常见问题 1. css中引用的图片路径出现错误 在css中,图片路径一般都是写相对路径,使用bundle后出现错误。处理方法:通过 new CssRewriteUrlTransform() 来解决 bundles.Add( new StyleBundle("~/Content/login") .Include("~/Content/login.css", new CssRewriteUrlTransform()) ); 2. css中使用@Import "base.css" 找不到对应的文件 解决:修改为 @import url("base.css"); import的相关文章:https://segmentfault.com/a/1190000000369549 参考: http://blog.csdn.net/zhou44129879/article/details/16818987 http://www.cnblogs.com/weishao/archive/2013/04/12/3015005.html
一、定义 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。 原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。 二、结构图 原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件: 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。 三、实现 namespace DesignPatterns.Prototype { class Program { static void Main(string[] args) { List<string> executors = new List<string>(); executors.Add("张三"); executors.Add("李四"); Plan plan = new Plan(); plan.SetName("重构前端登录界面"); plan.SetLevel(1); plan.SetStartdate(DateTime.Parse("2017-08-07")); plan.SetEnddate(DateTime.Parse("2017-08-09")); plan.SetExecutors(executors); Plan plan2 = plan.Clone(); plan2.SetName("后端接口改造"); plan2.SetLevel(2); plan2.SetStartdate(DateTime.Parse("2017-08-10")); plan2.SetEnddate(DateTime.Parse("2017-08-12")); Console.WriteLine("地址是否一样?" + (plan == plan2)); Console.WriteLine("plan.getName() == plan2.getName() " + (plan.GetName() == plan2.GetName())); Console.WriteLine("plan.getLevel() == plan2.getLevel() " + (plan.GetLevel() == plan2.GetLevel())); Console.WriteLine("plan.getStartdate() == plan2.getStartdate() " + (plan.GetStartdate() == plan2.GetStartdate())); Console.WriteLine("plan.getEnddate() == plan2.getEnddate() " + (plan.GetEnddate() == plan2.GetEnddate())); Console.WriteLine("plan.getExecutors() == plan2.getExecutors() " + (plan.GetExecutors() == plan2.GetExecutors())); Console.WriteLine("plan:" + plan.toString()); Console.WriteLine("plan2:" + plan2.toString()); //plan任务比较重,在给plan添加一个人 executors.Add("王五"); plan.SetExecutors(executors); Console.WriteLine(); Console.WriteLine("地址是否一样?" + (plan == plan2)); Console.WriteLine("plan.getName() == plan2.getName() " + (plan.GetName() == plan2.GetName())); Console.WriteLine("plan.getLevel() == plan2.getLevel() " + (plan.GetLevel() == plan2.GetLevel())); Console.WriteLine("plan.getStartdate() == plan2.getStartdate() " + (plan.GetStartdate() == plan2.GetStartdate())); Console.WriteLine("plan.getEnddate() == plan2.getEnddate() " + (plan.GetEnddate() == plan2.GetEnddate())); Console.WriteLine("plan.getExecutors() == plan2.getExecutors() " + (plan.GetExecutors() == plan2.GetExecutors())); Console.WriteLine("plan:" + plan.toString()); Console.WriteLine("plan2:" + plan2.toString()); } } /** * 计划 * 【浅拷贝】 */ public class Plan { //计划名称 private string _name; //任务级别 private int _level; //开始时间 private DateTime _startdate; //截止时间 private DateTime _enddate; //执行人员 private List<string> _executors = new List<string>(); public Plan Clone() { return this; } public string GetName() { return _name; } public void SetName(string name) { this._name = name; } public DateTime GetStartdate() { return _startdate; } public void SetStartdate(DateTime startdate) { this._startdate = startdate; } public DateTime GetEnddate() { return _enddate; } public void SetEnddate(DateTime enddate) { this._enddate = enddate; } public List<string> GetExecutors() { return _executors; } public void SetExecutors(List<string> executors) { this._executors = executors; } public int GetLevel() { return _level; } public void SetLevel(int level) { this._level = level; } public string toString() { return "[name=" + _name + ", level=" + _level + ", startdate=" + _startdate + ", enddate=" + _enddate + ", executors=" + _executors + "]"; } } } 四、适用场景 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、性能和安全要求的场景。 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 5、一个对象多个修改者的场景。 6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。 注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。 五、优缺点 优点: 1、对客户端隐藏具体的实现类型:原型模式的客户端,只知道原型接口的类型,并不知道具体的实现类型,从而减少了客户端对这些具体实现类型的依赖。 2、在运行时动态改变具体的实现类型:原型模式可以在运行期间,由客户来注册符合原型接口的实现类型,也可以动态的改变具体的实现类型,看起来接口没有任何变化,但其实运行的已经是另外一个类实例了。因为克隆一个原型就类似于实例化一个类。 缺点: 深度克隆方法实现会比较困难:原型模式最大的缺点就在于每个原型的子类都必须实现clone的操作,尤其在包含引用类型的对象时,clone方法会比较麻烦,必须要能够递归的让所有的相关对象都要正确的实现克隆。 参考: http://www.cnblogs.com/JsonShare/p/7300124.html http://www.runoob.com/design-pattern/prototype-pattern.html 欢迎阅读本系列文章:Head First设计模式之目录
一、示例演示 namespace TestConsole { class Program { static void Main(string[] args) { Console.WriteLine(""); List<string> list1 = new List<string>(); Test1(list1); Console.WriteLine(" list1:" + list1.Count()); // 总数量为 1 Console.WriteLine(""); Console.WriteLine("---- 亮瞎眼的分割线 ----"); Console.WriteLine(""); List<string> list2 = new List<string>(); Test2(list2); Console.WriteLine(" list2:" + list2.Count()); // 总数量仍为 0 Console.WriteLine(""); } static void Test1(List<string> list) { list.Add("1"); Console.WriteLine(" Test1():" + list.Count()); // 总数量为 1 } static void Test2(List<string> list) { List<string> list2 = new List<string>(); list2.Add("1"); list = list2; Console.WriteLine(" Test2():" + list.Count()); // 总数量为 1 } } 可以发现: 经过 Test1后,list 的元素数量由 0 变为 1 了, 经过 Test2后,list 的元素数量还是0。 二、解说 1.list类型是引用类型 2.引用本身是类似于一个“保存地址的值变量”所以从方法外部传入引用到方法里,那么其实引用本身是复制了一份副本来给方法里使用的,只是说这个复制的引用副本和之前的引用的内容(也就是所指向的对象内存地址)是一样的,所以通过引用操作对象的数据时,可以看到2个引用都操作的同一个对象;但如果你是修改了引用副本本身的值内容(将该引用指向了一个新的对象的内存地址),那么是不会影响到之前方法外的那个引用的,所以修改后会发现2个引用所指向的对象不同了而如果对象引用参数前加上了ref,那么方法参数所传递的不再是引用的副本,而是引用的地址了(即通过引用的地址找到引用,再读出引用里保存的内存地址值,再根据则个地址值去找到真正要操作的对象),所以如果此时你再修改这个引用的值时,会根据引用的地址找到方法外的那个引用,然后修改其内容,所以会发现方法外的引用也会指向新的对象了 3这里有三段代码 你可以看看,体会一下: (1) List<string> list=new List<string>(); ModifyList(list); Console.WriteLine(list.Count) private void ModifyList(List<string> list) { // 这里的list其实已经是一个引用副本了,但是所指向的内存地址仍然是原本方法外面的对象的,所以后面用该引用的Add方法所操作的,仍然是原本方法外面的对象的内存数据 list.Add("1"); list.Add("2"); list.Add("3"); } (2) List<string> list=new List<string>(); ModifyList(list); Console.WriteLine(list.Count) private void ModifyList(List<string> list) { list = new List<string>(); // 这里其实已经将引用指向了新的内存地址,所以后续的Add操作是在操作新对象的内存数据,而原来方法外的对象其实是没有受到影响的 list.Add("1"); list.Add("2"); list.Add("3"); } (3) List<string> list=new List<string>(); List<string> copy = list; // 复制一个引用 ModifyList(ref list); Console.WriteLine(copy.Count) // 复制的这个引用仍然指向原来最早的那个List Console.WriteLine(list.Count) // list这个引用已经在ModifyList方法里被修改了,指向的是在ModifyList方法里新new出来的对象了 private void ModifyList(ref List<string> list) { list = new List<string>(); // 因为有ref,所以这里其实已经将方法外原本的那个引用也指向了新的内存地址,所以后续的Add操作是在操作新对象的内存数据,并且方法外原本的那个引用也指向了这个新的对象 list.Add("1"); list.Add("2"); list.Add("3"); } 参考: http://www.cftea.com/c/2013/01/5724.asp http://bbs.csdn.net/topics/390600826
一、定义 定义了一个创建对象的接口, 但由子类决定要实例化的类是哪一个. 工厂方法让类把实例化推迟到子类 二、结构 1、抽象工厂角色:这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。2、具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。3、抽象产品角色:它是具体产品继承的父类或者是实现的接口。4、具体产品角色:具体工厂角色所创建的对象就是此角色的实例。 三、实现 namespace DesignPatterns.FactoryMethod { class Program { static void Main(string[] args) { Console.WriteLine("使用工厂方法进行计算"); Factory factory1 = new AddFactory(); Operation operation1 = factory1.CreateOperation(); operation1.numberA = 10; operation1.numberB = 20; Console.WriteLine("{0}+{1}={2}", operation1.numberA, operation1.numberB, operation1.GetResult()); Factory factory2 = new SubFactory(); Operation operation2 = factory2.CreateOperation(); operation2.numberA = 10; operation2.numberB = 20; Console.WriteLine("{0}-{1}={2}", operation2.numberA, operation2.numberB, operation2.GetResult()); Console.Read(); } } /// <summary> /// 抽象操作类 /// </summary> public abstract class Operation { public int numberA; public int numberB; public abstract int GetResult(); } /// <summary> /// 加法操作 /// </summary> public class AddOperation : Operation { public override int GetResult() { return (this.numberA + this.numberB); } } /// <summary> /// 减法操作 /// </summary> public class SubOperation : Operation { public override int GetResult() { return (this.numberA - this.numberB); } } public abstract class Factory { public abstract Operation CreateOperation(); } public class AddFactory : Factory { public override Operation CreateOperation() { return new AddOperation(); } } public class SubFactory : Factory { public override Operation CreateOperation() { return new SubOperation(); } } } 四、适用场景 类不知道自己要创建哪一个对象时 类用它的子类来指定创建哪个对象 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候 五、优缺点 优点: 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象。而且如何创建一个具体产品的细节完全封装在具体工厂内部,符合高内聚,低耦合。 在系统中加入新产品时,无需修改抽象工厂和抽象产品提供的接口,无需修改客户端,也无需修改其他的具体工厂和具体产品,很好的利用了封装和委托。 缺点: 在添加新产品时,需要编写新的具体产品类(其实这不算一个缺点,因为这是不可避免的),要增加与之对应的具体工厂类。 参考: http://www.cnblogs.com/yinxiangpei/articles/2366092.html http://www.cnblogs.com/ywqu/archive/2010/01/07/1640855.html http://www.cnblogs.com/Terrylee/archive/2006/01/04/310716.html 欢迎阅读本系列文章:Head First设计模式之目录
一、定义 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一种对象创建型模式。 二、结构 角色 Builder(抽象建造者):它为创建一个产品Product对象的各个部件指定抽象接口,在该接口中一般声明两类方法,一类方法是buildPartX(),它们用于创建复杂对象的各个部件;另一类方法是getResult(),它们用于返回复杂对象。Builder既可以是抽象类,也可以是接口。 ConcreteBuilder(具体建造者):它实现了Builder接口,实现各个部件的具体构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象。 Product(产品角色):它是被构建的复杂对象,包含多个组成部件,具体建造者创建该产品的内部表示并定义它的装配过程。在本类图中,产品类是一个具体的类,而非抽象类。实际编程中,产品类可以是由一个抽象类与它的不同实现组成,也可以是由多个抽象类与他们的实现组成。 Director(指挥者):指挥者又称为导演类,负责调用适当的建造者来组建产品,导演类一般不与产品类发生依赖关系,与导演类直接交互的是建造者类。一般来说,导演类被用来封装程序中易变的部分。 三、实现 背景:模拟生产各种笔(这里假设笔的零件生产是有顺序的:笔芯 -> 笔壳 -> 组装) namespace DesignPatterns.Builder { class Program { static void Main(string[] args) { PenDirector director = new PenDirector(); Pen ballpointpen = director.ConstructPen(new BallpointPenBuilder()); Pen brushpen = director.ConstructPen(new BrushPenBuilder()); } } /// <summary> /// 创建抽象产品类 -- 笔 /// </summary> public abstract class Pen { /**笔芯**/ private string _cartridge; /**外壳**/ private string _shell; public string GetCartridge() { return _cartridge; } public void SetCartridge(string cartridge) { this._cartridge = cartridge; } public string GetShell() { return _shell; } public void SetShell(string shell) { this._shell = shell; } } public interface IPenBuilder { /** * 生产笔芯 */ void BuildCartridge(); /** * 生产外壳 */ void BuildShell(); /** * 组装笔 */ Pen BuildPen(); } /// <summary> /// 具体产品类 -- 圆珠笔 /// </summary> public class BallpointPen : Pen { public BallpointPen() { Console.WriteLine("生产组装圆珠笔"); } } /// <summary> /// 具体产品类 -- 画笔 /// </summary> public class BrushPen : Pen { public BrushPen() { Console.WriteLine("生产组装画笔"); } } /// <summary> /// 建造者(具体) -- 圆珠笔builder /// </summary> public class BallpointPenBuilder : IPenBuilder { Pen _pen; public BallpointPenBuilder() { _pen = new BallpointPen(); } public void BuildCartridge() { _pen.SetCartridge("圆珠笔笔芯"); } public void BuildShell() { _pen.SetShell("圆珠笔外壳"); } public Pen BuildPen() { return _pen; } } /// <summary> /// 建造者(具体) -- 画笔builder /// </summary> public class BrushPenBuilder : IPenBuilder { Pen _pen; public BrushPenBuilder() { _pen = new BrushPen(); } public void BuildCartridge() { _pen.SetCartridge("画笔笔芯"); } public void BuildShell() { _pen.SetShell("画笔外壳"); } public Pen BuildPen() { return _pen; } } /// <summary> /// 导演类 Director /// </summary> public class PenDirector { public Pen ConstructPen(IPenBuilder penBuilder) { //生产笔芯 penBuilder.BuildCartridge(); //生产外壳 penBuilder.BuildShell(); //组装笔 return penBuilder.BuildPen(); } } } 四、适用场景 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。 对象的创建过程独立于创建该对象的类。在建造者模式中通过引入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类和客户类中。 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。 五、优缺点 优点: 封装性很好:使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在导演类中对整体而言可以取得比较好的稳定性。 扩展性很好:建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。 有效控制细节风险:由于具体的建造者是独立的,因此可以对建造者过程逐步细化,而不对其他的模块产生任何影响。 缺点: 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。 欢迎阅读本系列文章:Head First设计模式之目录
一、定义 给客户端提供一个接口,可以创建多个产品族中的产品对象 ,而且使用抽象工厂模式还要满足一下条件: 1)系统中有多个产品族,而系统一次只可能消费其中一族产品。 2)同属于同一个产品族的产品以其使用。 二、结构 抽象工厂模式的各个角色(和工厂方法一样): 1)抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。 2)具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。 3)抽象产品角色:它是具体产品继承的父类或者是实现的接口。 4)具体产品角色:具体工厂角色所创建的对象就是此角色的实例。 三、实现 namespace DesignPatterns.AbstractFactory { class Program { static void Main(string[] args) { IAbstractFactory factory = null; factory = new XiaomiFactory(); factory.ProductMobile(); factory.ProductMp3(); factory = new HuaweiFactory(); factory.ProductMobile(); factory.ProductMp3(); } } public abstract class AbstractProduct { } public abstract class Mobile : AbstractProduct { } public class XiaomiMobile : Mobile { } public class HuaweiMobile : Mobile { } public abstract class Mp3 : AbstractProduct { } public class XiaomiMp3 : Mp3 { } public class HuaweiMp3 : Mp3 { } public interface IAbstractFactory { Mobile ProductMobile(); Mp3 ProductMp3(); } public class XiaomiFactory : IAbstractFactory { public Mobile ProductMobile() { Console.WriteLine("生产小米手机."); return new XiaomiMobile(); } public Mp3 ProductMp3() { Console.WriteLine("生产小米MP3."); return new XiaomiMp3(); } } public class HuaweiFactory : IAbstractFactory { public Mobile ProductMobile() { Console.WriteLine("生产华为手机."); return new HuaweiMobile(); } public Mp3 ProductMp3() { Console.WriteLine("生产华为MP3."); return new HuaweiMp3(); } } } 四、适用场景 1、QQ 换皮肤,一整套一起换。 2、生成不同操作系统的程序。 五、优缺点 优点: 抽象工厂模式隔离了具体类的生产,使得客户并不需要知道什么被创建。 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。 缺点: 增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性 欢迎阅读本系列文章:Head First设计模式之目录
一、简介 当查询比较复杂时,需要很多判断或者跨方法传递参数时使用 二、扩展类 /// <summary> /// Expression表达式扩展操作类 /// 调用方法:repository.GetAll().AsExpandable().Where(predicate) /// </summary> public static class ExpressionExtensions { /// <summary> /// 以特定的条件运行组合两个Expression表达式 /// </summary> /// <typeparam name="T">表达式的主实体类型</typeparam> /// <param name="first">第一个Expression表达式</param> /// <param name="second">要组合的Expression表达式</param> /// <param name="merge">组合条件运算方式</param> /// <returns>组合后的表达式</returns> public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge) { var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f); var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body); return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters); } /// <summary> /// 以 Expression.AndAlso 组合两个Expression表达式 /// </summary> /// <typeparam name="T">表达式的主实体类型</typeparam> /// <param name="first">第一个Expression表达式</param> /// <param name="second">要组合的Expression表达式</param> /// <returns>组合后的表达式</returns> public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) { return first.Compose(second, Expression.AndAlso); } /// <summary> /// 以 Expression.OrElse 组合两个Expression表达式 /// </summary> /// <typeparam name="T">表达式的主实体类型</typeparam> /// <param name="first">第一个Expression表达式</param> /// <param name="second">要组合的Expression表达式</param> /// <returns>组合后的表达式</returns> public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) { return first.Compose(second, Expression.OrElse); } private class ParameterRebinder : ExpressionVisitor { private readonly Dictionary<ParameterExpression, ParameterExpression> _map; private ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map) { _map = map ?? new Dictionary<ParameterExpression, ParameterExpression>(); } public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp) { return new ParameterRebinder(map).Visit(exp); } protected override Expression VisitParameter(ParameterExpression node) { ParameterExpression replacement; if (_map.TryGetValue(node, out replacement)) node = replacement; return base.VisitParameter(node); } } } 三、如何使用 1.关于引用 using System.Linq;using System.Linq.Expressions; using LinqKit; 还需要引入扩展类的命名空间 2.使用示例 Expression<Func<User, bool>> pre; pre = s => s.NickName.Contains("李"); pre = pre.Or(s => s.NickName.Contains("陈")); pre = pre.And(s => s.CompanyId == "1");var data = _userRepository.GetAll().AsExpandable().Where(pre);
一、定义 享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。 享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。我们将通过创建 5 个对象来画出 20 个分布于不同位置的圆来演示这种模式。由于只有 5 种可用的颜色,所以 color 属性被用来检查现有的 Circle 对象。 二、结构 三、实现 namespace DesignPatterns.Flyweight { class Program { static Random ran = new Random(); static Random ranIndex = new Random(); static string[] colors = { "Red", "Green", "Blue", "White", "Black" }; static void Main(string[] args) { for (int i = 0; i < 20; ++i) { Circle circle = (Circle)ShapeFactory.GetCircle(GetRandomColor()); circle.SetX(GetRandomX()); circle.SetY(GetRandomY()); circle.SetRadius(100); circle.Draw(); } } private static string GetRandomColor() { int randKey = ranIndex.Next(0, colors.Count() - 1); return colors[randKey]; } private static int GetRandomX() { var d = GetDouble(); return (int)(d * 100); } private static int GetRandomY() { var d = GetDouble(); return (int)(d * 100); } private static double GetDouble() { int randKey = ran.Next(1, 100); return randKey * (1.0) / 100; } } public interface IShape { void Draw(); } public class Circle : IShape { private String color; private int x; private int y; private int radius; public Circle(String color) { this.color = color; } public void SetX(int x) { this.x = x; } public void SetY(int y) { this.y = y; } public void SetRadius(int radius) { this.radius = radius; } public void Draw() { Console.WriteLine("Circle: Draw() [Color : " + color + ", x : " + x + ", y :" + y + ", radius :" + radius); } } public class ShapeFactory { private static Dictionary<String, IShape> circleMap = new Dictionary<String, IShape>(); public static IShape GetCircle(String color) { Circle circle; if (circleMap.ContainsKey(color)) { circle = (Circle)circleMap[color]; } else { circle = new Circle(color); circleMap[color] = circle; Console.WriteLine("Creating circle of color : " + color); } return circle; } } } 四、使用场景 1、系统中有大量对象。 2、这些对象消耗大量内存。 3、这些对象的状态大部分可以外部化。 4、这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。 5、系统不依赖于这些对象身份,这些对象是不可分辨的。 五、优缺点 优点: 1)享元模式的优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份。 2)享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。 缺点: 1)享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。 2)为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。
一、定义 定义:为其他对象提供一种代理以控制对这个对象的访问 在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。 二、结构 代理模式一般会有三个角色: 抽象角色(Subject):指代理角色和真实角色对外提供的公共方法,一般为一个接口 真实角色(RealSubject):需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,以便供代理角色调用。 代理角色(Proxy):需要实现抽象角色接口,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。 三、实现 1.静态代理 namespace DesignPatterns.Proxy { class Program { static void Main(string[] args) { RealStar star = new RealStar(); Agent agent = new Agent(star); agent.DiscussCooperation(); agent.SignContract(); agent.PlayRoleInMovie(); agent.CollectMoney(); } } public interface IMovieStar { /// <summary> /// 找电影合作 /// </summary> void DiscussCooperation(); /// <summary> /// 签合同 /// </summary> void SignContract(); /// <summary> /// 出演电影角色 /// </summary> void PlayRoleInMovie(); /// <summary> /// 收演出费 /// </summary> void CollectMoney(); } public class RealStar : IMovieStar { public void DiscussCooperation() { Console.WriteLine("星爷找电影合作"); } public void SignContract() { Console.WriteLine("星爷签合同"); } public void PlayRoleInMovie() { Console.WriteLine("星爷演出"); } public void CollectMoney() { Console.WriteLine("星爷收费"); } } public class Agent : IMovieStar { private IMovieStar _star; public Agent(IMovieStar star) { _star = star; } public void DiscussCooperation() { Console.WriteLine("经纪人找电影合作"); } public void SignContract() { Console.WriteLine("经纪人签合同"); } public void PlayRoleInMovie() { _star.PlayRoleInMovie(); } public void CollectMoney() { Console.WriteLine("经纪人收费"); } } } 四、使用场景 按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。 五、优缺点 优点: 1、代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度; 2、代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了保护目标对象的作用。 缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、增加了代码维护的复杂度,实现代理模式需要额外的工作,有些代理模式的实现非常复杂。 欢迎阅读本系列文章:Head First设计模式之目录
一、简介 委托是一种类型,由关键字delegate声明。确切的说,委托是一种可用于封装命名或者匿名方法的引用类型。 它类似于 C++ 中的函数指针,而且是类型安全和可靠的。 委托类型的声明与方法签名相似,有一个返回值和任意数目任意类型的参数。必须使用具有兼容返回类型和输入参数的方法或 lambda 表达式实例化委托。 委托允许将方法作为参数进行传递。 委托可用于定义回调方法。 委托可以链接在一起;例如,可以对一个事件调用多个方法。 方法不必与委托签名完全匹配。 二、Delegate Delegate至少0个参数,至多32个参数,可以无返回值,也可以指定返回值类型。 public delegate int MethodDelegate(int x, int y); private static MethodDelegate method; static void Main(string[] args) { method = new MethodDelegate(Add); Console.WriteLine(method(10,20)); Console.ReadKey(); } private static int Add(int x, int y) { return x + y; } 三、Func<T> 在使用 Func<T, TResult> 委托时,不必显式定义一个封装只有一个参数的方法的委托。 以下示例简化了此代码,它所用的方法是实例化 Func<T, TResult> 委托,而不是显式定义一个新委托并将命名方法分配给该委托。 public class GenericFunc { public static void Main() { // 依旧是用命名方法实例化委托类型 Func<string, string> convertMethod = UppercaseString; string name = "Dakota"; // 依旧是通过委托实例调用该方法 Console.WriteLine(convertMethod(name)); } private static string UppercaseString(string inputString) { return inputString.ToUpper(); } } 下面的示例演示如何声明和使用 Func<T, TResult> 委托。 此示例声明一个 Func<T, TResult> 变量,并为其分配了一个将字符串中的字符转换为大写的 lambda 表达式。 随后将封装此方法的委托传递给Enumerable.Select 方法,以将字符串数组中的字符串更改为大写。 static class Func { static void Main(string[] args) { // 声明了一个Func委托类型的变量selector并用Lambda表达式进行实例化 // 这个Lambda表达式将用来获取一个字符串并将这个字符串转化为大写并返回 Func<string, string> selector = str => str.ToUpper(); // 创建一个字符串数组 string[] words = { "orange", "apple", "Article", "elephant" }; // 依次遍历这个字符串数组并调用委托实例selector进行处理 IEnumerable<String> aWords = words.Select(selector); // 输出结果到控制台 foreach (String word in aWords) Console.WriteLine(word); } } /* This code example produces the following output: ORANGE APPLE ARTICLE ELEPHANT */ 四、Action<T> Action 委托:没有传入参数,也没有返回类型,即Void。如: void Main(string[] args) { Action say = SayHello; say(); } public static void SayHello( ) { Console.WriteLine("Say Hello"); } Action<T> 委托:传入参数为T,没有返回类型。如: void Main(string[] args) { Action<string> say = SayHello; say("Hello"); } public static void SayHello(string word ) { Console.WriteLine(word); } 其实Action与Func的用法差不多,差别只是一个有返回类型,一个没有返回类型,当然Action也可以接匿名方法和Lambda表达式。 匿名方法: void Main(string[] args) { Action<string> say = delegate(string word) { Console.WriteLine(word); }; say("Hello Word"); } Lambda表达式: static void Main(string[] args) { Action<string> say = s => Console.WriteLine(s); say("Hello Word"); } 五、Predicate<T> 泛型委托:表示定义一组条件并确定指定对象是否符合这些条件的方法。此委托由 Array 和 List 类的几种方法使用,用于在集合中搜索元素。 void Main(string[] args) { Point[] points = { new Point(100, 200), new Point(150, 250), new Point(250, 375), new Point(275, 395), new Point(295, 450) }; Point first = Array.Find(points, ProductGT10); Console.WriteLine("Found: X = {0}, Y = {1}", first.X, first.Y); Console.ReadKey(); } private static bool ProductGT10(Point p) { if (p.X * p.Y > 100000) { return true; } else { return false; } } 使用带有 Array.Find 方法的 Predicate 委托搜索 Point 结构的数组。 如果 X 和 Y 字段的乘积大于 100,000,此委托表示的方法 ProductGT10 将返回 true。 Find 方法为数组的每个元素调用此委托,在符合测试条件的第一个点处停止。 六、总结 Delegate至少0个参数,至多32个参数,可以无返回值,也可以指定返回值类型 Func可以接受0个至16个传入参数,必须具有返回值 Action可以接受0个至16个传入参数,无返回值 Predicate只能接受一个传入参数,返回值为bool类型 参考文章: http://www.cnblogs.com/xcsn/p/4520081.html http://www.cnblogs.com/newbies/p/delegate.html https://msdn.microsoft.com/zh-cn/library/bb534960.aspx http://www.cnblogs.com/Gyoung/archive/2013/04/04/2997050.html http://www.cnblogs.com/zyqgold/archive/2011/01/02/1924005.html http://www.cnblogs.com/akwwl/p/3232679.html
一、定义 桥接模式(Bridge Pattern),将抽象部分与它的实现部分分离,使的抽象和实现都可以独立地变化。 主要解决:在多维可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。 何时使用:实现系统可能有多个角度分类,每一种角度都可能变化。 如何解决:把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。 注意事项:对于两个独立变化的维度,使用桥接模式再适合不过了。 二、结构 三、实现 一般写法 namespace DesignPatterns.Bridge { class Program { static void Main(string[] args) { CarOnSpeedWay carOnSpeedWay = new CarOnSpeedWay(); BusOnSpeedWay busOnSpeedWay = new BusOnSpeedWay(); CarOnStreet carOnStreet = new CarOnStreet(); BusOnStreet busOnStreet = new BusOnStreet(); carOnSpeedWay.Run(); busOnSpeedWay.Run(); carOnStreet.Run(); busOnStreet.Run(); Console.WriteLine("==== 这是分割线 ====="); } } public class Road { public virtual void Run() { Console.WriteLine("在路上跑"); } } public class SpeedWay : Road { public override void Run() { Console.WriteLine("在高速上跑"); } } public class Street : Road { public override void Run() { Console.WriteLine("在大街上跑"); } } public class CarOnSpeedWay : SpeedWay { public override void Run() { Console.WriteLine("汽车在高速上跑"); } } public class BusOnSpeedWay : SpeedWay { public override void Run() { Console.WriteLine("大巴在高速上跑"); } } public class CarOnStreet : Street { public override void Run() { Console.WriteLine("汽车在大街上跑"); } } public class BusOnStreet : Street { public override void Run() { Console.WriteLine("大巴在大街上跑"); } } } 显示结果 可以看到,如果又增加了道路类型(乡村道路)、车型(电动车、摩托车),那么就变的非常复杂庞大,扩展困难,又造成类爆炸。使用桥接模式来避免这个问题,代码如下 namespace DesignPatterns.Bridge { class Program { static void Main(string[] args) { Console.WriteLine("==== 这是分割线 ====="); AbstractRoad road1 = new SpeedWay(new Car()); road1.Run(); Console.WriteLine(".............."); AbstractRoad road2 = new Street(new Bus()); road2.Run(); Console.Read(); } } public abstract class AbstractCar { public abstract void Run(); } public class Car : AbstractCar { public override void Run() { Console.WriteLine("汽车在"); } } public class Bus : AbstractCar { public override void Run() { Console.WriteLine("大巴在"); } } public abstract class AbstractRoad { protected AbstractCar car; public AbstractCar Car { set { car = value; } } public abstract void Run(); } public class SpeedWay : AbstractRoad { public SpeedWay(AbstractCar car) { this.car = car; } public override void Run() { car.Run(); Console.WriteLine("高速上跑"); } } public class Street : AbstractRoad { public Street(AbstractCar car) { this.car = car; } public override void Run() { car.Run(); Console.WriteLine("大街上跑"); } } } 四、使用场景 在以下情况下应当使用桥接模式: 如果一个系统需要在构件的抽象化角色和具体化角色之间添加更多的灵活性,避免在两个层次之间建立静态的联系。 设计要求实现化角色的任何改变不应当影响客户端,或者实现化角色的改变对客户端是完全透明的。 需要跨越多个平台的图形和窗口系统上。 一个类存在两个独立变化的维度,且两个维度都需要进行扩展。 实际遇到过的应用: 非常典型的例子 -- JDBC驱动程序 人力资源系统中的奖金计算模块: 奖金分类:个人奖金,团体奖金,项目奖金,激励奖金 部门分类:人事部,销售部,研发部 OA系统中的消息处理: 业务类型:普通消息,加急消息,特急消息 发送消息方式:系统内消息,手机短信,邮件 五、优缺点 优点: 把抽象接口与其实现解耦。 抽象和实现可以独立扩展,不会影响到对方。 实现细节对客户透明,对用于隐藏了具体实现细节。 缺点: 增加了系统的复杂度 参考文章: http://www.runoob.com/design-pattern/bridge-pattern.html http://www.cnblogs.com/houleixx/archive/2008/02/23/1078877.html http://www.cnblogs.com/JsonShare/p/7233342.html http://www.cnblogs.com/zhili/p/BridgePattern.html 欢迎阅读本系列文章:Head First设计模式之目录
一、定义 将对象组合成树形结构来表现"整体-部分"层次结构。 组合能让客户以一致的方法处理个别对象以及组合对象。 主要部分可以被一致对待问题. 在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。 二、结构 组合模式主要包含三个角色 1.Component(抽象构件) : 为参加组合的对象定义了公共接口和默认行为,声明一个接口用于访问和管理Component子部件。 2.Composite(容器构件):容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。 3.Leaf(叶子构件): 代表参加组合的叶子对象(叶子没有后继)。定义组成原始对象的行为。 三、实现 class Program { static void Main(string[] args) { MenuComponent menuComponent = new MenuItem("网站"); MenuComponent menuComponent1 = new MenuItem("主页"); MenuComponent menuComponent2 = new MenuItem("产品页"); MenuComponent menuComponent21 = new MenuLeaf("CRM"); MenuComponent menuComponent22 = new MenuLeaf("ERP"); menuComponent.Add(menuComponent1); menuComponent.Add(menuComponent2); menuComponent2.Add(menuComponent21); menuComponent2.Add(menuComponent22); menuComponent2.Remove(menuComponent21); } } public abstract class MenuComponent { protected MenuComponent(string name) { Name = name; } public string Name { get; set; } public abstract void Add(MenuComponent menuComponent); public abstract void Remove(MenuComponent menuComponent); } public class MenuItem : MenuComponent { public MenuItem(string name) : base(name) { } public override void Add(MenuComponent menuComponent) { Console.WriteLine(Name + "增加: " + menuComponent.Name); } public override void Remove(MenuComponent menuComponent) { Console.WriteLine(Name + "移除: " + menuComponent.Name); } } public class MenuLeaf : MenuComponent { public MenuLeaf(string name) : base(name) { } public override void Add(MenuComponent menuComponent) { throw new Exception("不能增加"); } public override void Remove(MenuComponent menuComponent) { throw new Exception("不能移除"); } } 网站菜单的简单实现 四、适用场景 在以下情况下应该考虑使用组合模式: 1、 在对象具有部分-整体层次结构,可以选用组合模式,把整体和部分的操作统一起来,使得层次结构实现更简单,从外部来使用这个层次结构也简单。 2、 系统中需要处理一个树形结构。 3、 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型。 实际应用: XML解析 、织结构树处理、 件系统设计 五、优缺点 优点: 组合模式使得客户端代码可以一致地处理对象和对象容器,无需关系处理的单个对象,还是组合的对象容器。 将”客户代码与复杂的对象容器结构“解耦。 可以更容易地往组合对象中加入新的构件。 缺点: 1.在增加新构件时很难对容器中的构件类型进行限制。 2.使设计变得更加抽象、复杂 六、模式扩展 组合模式在.NET 中最典型的应用就是应用与WinForms和Web的开发中,在.NET类库中,都为这两个平台提供了很多现有的控件,然而System.Windows.Forms.dll中System.Windows.Forms.Control类就应用了组合模式,因为控件包括Label、TextBox等这样的简单控件,同时也包括GroupBox、DataGrid这样复合的控件,每个控件都需要调用OnPaint方法来进行控件显示,为了表示这种对象之间整体与部分的层次结构,微软把Control类的实现应用了组合模式(确切地说应用了透明式的组合模式)。
1.input回车事件不执行导致页面刷新 场景:在文本框中输入关键字按回车,页面自动刷新了 <form name="keywordForm" method="post" action=""> <p id="profile_nav"> <label for="profile"> 关键字搜索: </label> <input style="width:80; height:20" type="text" name="keyword" onkeypress="searchKeywordKeyboard(event)" /> <input type="button" value="搜索" onClick="searchKeyword()"> </p> </form> 解决方法1: 一个表单下,如果只有一个文本框时,按下回车将会触发表单的提交事件。 既然是只有一个文本框才会出问题,那么可以加一个隐藏的文本框 解决方法2:(推荐) <form name="keywordForm" method="post" action="" onsubmit="return false;"> 就是在表单 form 后面加上一个 onsubmit 事件,返回 false,来阻止 form 提交。 解决方法3:(不推荐) document.onkeydown=function(e){ var e = e || event; var currKey = e.keyCode || e.which || e.charCode;//支持IE,FireFox if (currKey == 13) { return false; } } 解决方法4: <input type="text" onkeydown="return ClearSubmit(event)" /> function ClearSubmit(e) { if (e.keyCode == 13) { return false; } }