设计模式

如何正确使用设计模式? 设计模式要活学活用,不要生搬硬套。想要游刃有余地使用设计模式,需要打下牢固的程序设计语言基础、夯实自己的编程思想、积累大量的时间经验、提高开发能力。目的都是让程序低耦合,高复用,高内聚,易扩展,易维护。 1. 需求驱动 不仅仅是功能性需求,需求驱动还包括性能和运行时的需求,如软件的可维护性和可复用性等方面。设计模式是针对软件设计的,而软件设计是针对需求的,一定不要为了使用设计模式而使用设计模式,否则可能会使设计变得复杂,使软件难以调试和维护。 2. 分析成功的模式应用项目 对现有的应用实例进行分析是一个很好的学习途径,应当注意学习已有的项目,而不仅是学习设计模式如何实现,更重要的是注意在什么场合使用设计模式。 3. 充分了解所使用的开发平台 设计模式大部分都是针对面向对象的软件设计,因此在理论上适合任何面向对象的语言,但随着技术的发展和编程环境的改善,设计模式的实现方式会有很大的差别。在一些平台下,某些设计模式是自然实现的。 不仅指编程语言,平台还包括平台引入的技术。例如,Java EE 引入了反射机制和依赖注入,这些技术的使用使设计模式的实现方式产生了改变。 4. 在编程中领悟模式 软件开发是一项实践工作,最直接的方法就是编程。没有从来不下棋却熟悉定式的围棋高手,也没有不会编程就能成为架构设计师的先例。掌握设计模式是水到渠成的事情,除了理论只是和实践积累,可能会“渐悟”或者“顿悟”。 5.避免设计过度 设计模式解决的是设计不足的问题,但同时也要避免设计过度。一定要牢记简洁原则,要知道设计模式是为了使设计简单,而不是更复杂。如果引入设计模式使得设计变得复杂,只能说我们把简单问题复杂化了,问题本身不需要设计模式。 开闭原则 开闭原则(Open Closed Principle,OCP)由勃兰特·梅耶(Bertrand Meyer)提出,他在 1988 年的著作《面向对象软件构造》(Object Oriented Software Construction)中提出:软件实体应当对扩展开放,对修改关闭(Software entities should be open for extension,but closed for modification),这就是开闭原则的经典定义。 这里的软件实体包括以下几个部分: 项目中划分出的模块 类与接口 方法 开闭原则的含义是:当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。 开闭原则的作用 开闭原则是面向对象程序设计的终极目标,它使软件实体拥有一定的适应性和灵活性的同时具备稳定性和延续性。具体来说,其作用如下。 1. 对软件测试的影响 软件遵守开闭原则的话,软件测试时只需要对扩展的代码进行测试就可以了,因为原有的测试代码仍然能够正常运行。 2. 可以提高代码的可复用性 粒度越小,被复用的可能性就越大;在面向对象的程序设计中,根据原子和抽象编程可以提高代码的可复用性。 3. 可以提高软件的可维护性 遵守开闭原则的软件,其稳定性高和延续性强,从而易于扩展和维护。 开闭原则的实现方法 可以通过“抽象约束、封装变化”来实现开闭原则,即通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。 因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。 里氏替换原则 里氏替换原则(Liskov Substitution Principle,LSP)由麻省理工学院计算机科学实验室的里斯科夫(Liskov)女士在 1987 年的“面向对象技术的高峰会议”(OOPSLA)上发表的一篇文章《数据抽象和层次》(Data Abstraction and Hierarchy)里提出来的,她提出:继承必须确保超类所拥有的性质在子类中仍然成立(Inheritance should ensure that any property proved about supertype objects also holds for subtype objects)。 ...

January 10, 2022 · 3 min · LiuYingbo

c# async/await

Talk is cheap, Show you the code first! private void button1_Click(object sender, EventArgs e) { Console.WriteLine("111 balabala. My Thread ID is :" + Thread.CurrentThread.ManagedThreadId); AsyncMethod(); Console.WriteLine("222 balabala. My Thread ID is :" + Thread.CurrentThread.ManagedThreadId); } private async Task AsyncMethod() { var ResultFromTimeConsumingMethod = TimeConsumingMethod(); string Result = await ResultFromTimeConsumingMethod + " + AsyncMethod. My Thread ID is :" + Thread.CurrentThread.ManagedThreadId; Console.WriteLine(Result); //返回值是Task的函数可以不用return } //这个函数就是一个耗时函数,可能是IO操作,也可能是cpu密集型工作。 private Task<string> TimeConsumingMethod() { var task = Task.Run(()=> { Console.WriteLine("Helo I am TimeConsumingMethod. My Thread ID is :" + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(5000); Console.WriteLine("Helo I am TimeConsumingMethod after Sleep(5000). My Thread ID is :" + Thread.CurrentThread.ManagedThreadId); return "Hello I am TimeConsumingMethod"; }); return task; } 异步方法的结构 上面是一个的使用async/await的例子(为了方便解说原理我才写的这样复杂的)。 使用async/await能非常简单的创建异步方法,防止耗时操作阻塞当前线程。 使用async/await来构建的异步方法,逻辑上主要有下面三个结构: ...

December 5, 2021 · 3 min · LiuYingbo

C#反射机制

C#反射机制 资料转载自知乎:https://zhuanlan.zhihu.com/p/41282759 何为反射? 首先我们通过两个实例来说明反射的大体概念。 B超:大家体检的时候大概都做过B超,B超可以透过肚皮探测到你内脏的生理情况。这是如何做到的呢?B超是B型超声波,它可以透过肚皮通过向你体内发射B型超声波,当超声波遇到内脏壁的时候就会产生一定的“回音”反射,然后把“回音”进行处理就可以显示出内脏的情况了。 地球内部结构:地球的内部结构大体可以分为三层:地壳、地幔和地核。如何在地球表面不用深入地球内部就知道其内部的构造呢?我们可以向地球发射“地震波”,“地震波”分两种一种是“横波”,另一种是“纵波”。“横波”只能穿透固体,而“纵波”既可穿透固体又可以穿透液体。通过在地面对纵波和横波的反回情况,我们就可以大体断定地球内部的构造了。 大家注意到这两个例子的共同特点,就是从一个对象的外部去了解对象内部的构造,而且都是利用了波的反射功能。在.NET中的反射也可以实现从对象的外部来了解对象(或程序集)内部结构的功能,哪怕你不知道这个对象(或程序集)是个什么东西,另外.NET中的反射还可以运态创建出对象并执行它其中的方法。 反射是.NET中的重要机制,通过反射,可以在运行时获得程序或程序集中每一个类型(包括类、结构、委托、接口和枚举等)的成员和成员的信息。有了反射,即可对每一个类型了如指掌。另外我还可以直接创建对象,即使这个对象的类型在编译时还不知道。 为什么使用反射,而不直接引用它的dll或者类型呢? 例如你有个main.exe,需要使用say.dll,draw.dll,突然客户说我们要添加一个跑的功能,那么只需要按照我们约定的规则做一个run的dll,之前的main.exe不需要做任何修改(就是不需要再去导入run.dll,其中需要其他的设计来规范),在main.exe中就能直接使用run.dll了。 其实,我们已经在不自觉地使用它了,举个最简单的例子,当你在VS的设计器里拖入一个控件后,设计器会通过反射获取这个控件的属性,并提供你进行设置。那么,问题来了,为什么要用反射呢?因为设计器在做的时候,根本不可能预知将来有什么控件会被你拖入进去。 反射的用途简要介绍 反射的用途大体总结如下,我们会在下面详细的进行介绍。 (1)使用Assembly定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例。 (2)使用Module了解包含模块的程序集以及模块中的类等,还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。 (3)使用ConstructorInfo了解构造函数的名称、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。 (4)使用MethodInfo了解方法的名称、返回类型、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。 (5)使用FiedInfo了解字段的名称、访问修饰符(如public或private)和实现详细信息(如static)等,并获取或设置字段值。 (6)使用EventInfo了解事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等,添加或移除事件处理程序 (7)使用PropertyInfo了解属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,获取或设置属性值。 (8)使用ParameterInfo了解参数的名称、数据类型、是输入参数还是输出参数,以及参数在方法签名中的位置等。 反射用到的主要类: System.Type 类–通过这个类可以访问任何给定数据类型的信息。 System.Reflection.Assembly类–它可以用于访问给定程序集的信息,或者把这个程序集加载到程序中。 System.Type类:System.Type 类对于反射起着核心的作用。但它是一个抽象的基类,Type有与每种数据类型对应的派生类,我们使用这个派生类的对象的方法、字段、属性来查找有关该类型的所有信息。获取给定类型的Type引用有3种常用方式: Type类的属性: Name 数据类型名 FullName 数据类型的完全限定名(包括命名空间名) Namespace 定义数据类型的命名空间名 IsAbstract 指示该类型是否是抽象类型 IsArray 指示该类型是否是数组 IsClass 指示该类型是否是类 IsEnum 指示该类型是否是枚举 IsInterface 指示该类型是否是接口 IsPublic 指示该类型是否是公有的 IsSealed 指示该类型是否是密封类 IsValueType 指示该类型是否是值类型 Type类的方法: GetConstructor(), GetConstructors():返回 ConstructorInfo类型,用于取得该类的构造函数的信息 GetEvent(), GetEvents():返回EventInfo类型,用于取得该类的事件的信息 GetField(), GetFields():返回FieldInfo类型,用于取得该类的字段(成员变量)的信息 GetInterface(), GetInterfaces():返回InterfaceInfo类型,用于取得该类实现的接口的信息 GetMember(), GetMembers():返回MemberInfo类型,用于取得该类的所有成员的信息 GetMethod(), GetMethods():返回MethodInfo类型,用于取得该类的方法的信息 GetProperty(), GetProperties():返回PropertyInfo类型,用于取得该类的属性的信息可以调用这些成员,其方式是调用Type的InvokeMember()方法,或者调用MethodInfo, PropertyInfo和其他类的Invoke()方法。 ...

December 5, 2021 · 1 min · LiuYingbo

C#学习-C# 3.0

C# 3.0 版本 C# 3.0 新增的语法特性,如下: 自动实现的属性 匿名类型 查询表达式(LINQ) 表达式 表达式树 扩展方法 隐式类型本地变量 分部方法 对象和集合初始值设定项 自动实现属性 这个特性非常简单,代码如下: public class PropertyExample { // C# 3.0之前 private string mNickName; public string NickName { get { return mNickName; } set { mNickName = value; } } // C# 3.0 public string Name { get; set; } } 非常简单,看代码就懂了。 匿名类型 代码也很简单,如下: var person = new {Title = "Name"}; Debug.Log(person.Title); lambda 表达式 lambda 表达式非常简单,就是匿名方法的一种表现,如下: using System; public class LambdaExpressionExample { void Func() { } void Main() { // C# 1.0 Action action1 = new Action(Func); // C# 2.0 Action actionB = delegate { }; // C# 3.0 Action acionC = () => { }; } } 表达式树 表达式树实际上是 Expression 这个 API 的使用,先看下代码,如下: ...

December 5, 2021 · 6 min · LiuYingbo

C#学习-C# 2.0

C# 2.0 版本 在 C# 2.0 版本提供的特性如下: 泛型 分部类型(partial) 匿名方法 可以为 null 的值类型 迭代器 协变和逆变 getter/setter 单独可访问性 方法组转换(委托) 静态类 委托推断 泛型 最常用的 List 就是泛型的一个应用。 C# 文档地址:泛型 泛型 API 的设计 要设计一个泛型 的 API 非常简单,看以下代码中的 GetTypeName 方法。 using UnityEngine; namespace QFramework.Master { public class ReflectionExample : MonoBehaviour { string GetTypeName<T>() { var type = typeof(T); return type.Name; } private void Awake() { Debug.Log(GetTypeName<string>()); } } } // 输出结果: // String 泛型可以把一个类或者一个方法,当做一个模板,而这个模板所需要填充的内容则是定义的各种类型,所以泛型可以最大限度地实现代码的复用。 C# 什么要实现泛型? C# 实现泛型这个特性,肯定是为了解决开发者遇到的实际问题的,要想知道泛型具体解决了什么问题,那么就要回过头看看没实现泛型之前的 C# 语言有什么样的问题? 在 C# 实现泛型之前,我们只能使用 ArrayList 来充当不定长的数组。 ...

December 5, 2021 · 9 min · LiuYingbo

javascript 高性能数组去重

一、测试模版 数组去重是一个老生常谈的问题,网上流传着有各种各样的解法 为了测试这些解法的性能,我写了一个测试模版,用来计算数组去重的耗时 // distinct.js let arr1 = Array.from(new Array(100000), (x, index)=>{ return index }) let arr2 = Array.from(new Array(50000), (x, index)=>{ return index+index }) let start = new Date().getTime() console.log('开始数组去重') function distinct(a, b) { // 数组去重 } console.log('去重后的长度', distinct(arr1, arr2).length) let end = new Date().getTime() console.log('耗时', end - start) 这里分别创建了两个长度为 10W 和 5W 的数组 然后通过 distinct() 方法合并两个数组,并去掉其中的重复项 数据量不大也不小,但已经能说明一些问题了 二、Array.filter() + indexOf 这个方法的思路是,将两个数组拼接为一个数组,然后使用 ES6 中的 Array.filter() 遍历数组,并结合 indexOf 来排除重复项 function distinct(a, b) { let arr = a.concat(b); return arr.filter((item, index)=> { return arr.indexOf(item) === index }) } 这就是我被吐槽的那个数组去重方法,看起来非常简洁,但实际性能。。。 是的,现实就是这么残酷,处理一个长度为 15W 的数组都需要 8427ms 三、双重 for 循环 最容易理解的方法,外层循环遍历元素,内层循环检查是否重复 当有重复值的时候,可以使用 push(),也可以使用 splice() function distinct(a, b) { let arr = a.concat(b); for (let i=0, len=arr.length; i<len; i++) { for (let j=i+1; j<len; j++) { if (arr[i] == arr[j]) { arr.splice(j, 1); // splice 会改变数组长度,所以要将数组长度 len 和下标 j 减一 len--; j--; } } } return arr } 但这种方法占用的内存较高,效率也是最低的 ...

December 2, 2021 · 2 min · LiuYingbo

C#学习-C# 1.0

C# 1.0 我们先罗列一下 C# 1.0 发布时所包含的语法特性,如下: 类(class) 结构(struct) 接口(interface) 事件(event) 属性(property) 委托(delegates) 表达式 语句 特性(有时候也叫属性)(Attribute) C# 1.0 的特性只有以上这些。 基本上它涵盖了 C# 中最常用、最核心、最基础部分的内容。C# 使用进阶,一些非常基础的东西就不记录了。 类 单纯从功能上来说,类可以: 包含变量(属性) 包含方法(行为) 继承 用来创建对象 面向对象与面向过程 有了类我们比较容易地进行面向对象编程,没有类当然也可以面向对象编程,只不过会不那么容易做到。 那么什么是面向对象编程呢? 我的理解只有两个字,建模。 建模就是对需要实现的功能或者需要解决的问题建立业务模型。 比如要实现购买商品功能,就需要我们创建商品对象,还要把购买商品的行为归属到另一个对象中,这个对象有可能是顾客对象,也有可能是平台对象。 这就是非常典型的面向对象建模案例。 那么如果是面向过程中的购买商品会是什么样的呢? 我们也许这样考虑,购买商品先创建一个购买商品函数,然后函数可能需要调用数据库,那就再写一个调用数据库的函数交媾购买商品函数调用,依次类推。 其实要区分面向对象编程与面向过程编程非常容易。 面向对象编程是以对象为基础进行思考的,而面向过程是以功能(函数)为基础进行思考的。 这就是两者的差别。 面向对象与面向过程是对立的吗? 不是对立的,面向对象更擅设计,而面向过程更擅长实现。 在使用 C# 的时候,两种编程思维都是会用到的。 没有类如何面向对象编程? 面向对象编程,当然要有对象了,如果没有类的话,其实也可以实现面向对象编程。 c 语言虽然不支持类,但是也可以通过宏或者一些模拟的手段支持类和对象的。 Lua 语言也没有提供类功能,但是通过元表也是能够模拟类和对象的。 OK,到此,面向对象这个话题可能与我们的专栏话题不太对题,但是一想到类,就不自觉地扯到面向对象这个话题了。 总是面向对象编程的核心就是两个字:建模。 引用类型和值类型 几乎在每一本编程语言书籍上都会谈到引用类型和值类型这两个概念。 具体的引用类型和值类型相关的细节,大家在网上找各种文章就可以了。 引用类型,是用类创建的类型就是引用类型。 值类型包含基础类型和结构体(struct)创建出来的类型。 如下: 就是这么简单。 定义 值类型直接存储其值,而引用类型存储对其值的引用 值类型 和 引用类型 是非常重要且非常基础的话题,所以想深入研究,网上能够找到一大堆文章。 类是引用类型 记住这句话就行。 ...

December 1, 2021 · 7 min · LiuYingbo

四叉树碰撞检测算法

实现原理 四叉树是什么? 四叉树本身是树结构的一种,如果物体过多的话,先根据物体所处位置划分成四块,如果每个块的中的物体数量还是很多的话,继续划分成四块。如下图红线所示。 检测的时候,就是根据待测试对象的位置,去找属于哪个块,再把这个块中的物体告诉你。如下图中的绿色物体。 那么怎么实现四叉树呢?用好 github 就行了(误),搜了一下,找到一个库,直接拿来改改就行了。 GitHub - timohausmann/quadtree-js: A lightweight quadtree implementation for javascript 代码解析 构造函数 function Quadtree( bounds, max_objects, max_levels, level ) { this.max_objects = max_objects || 10; //每个区域可以容纳的最大对象数,超过就需要划分 this.max_levels = max_levels || 4; //最多划几层四叉树 this.level = level || 0; //当前树或子树的层,根为0 this.bounds = bounds; //bounds就是对象集,每个点包括x,y,width,height(有些实现是圆形,这个是矩形) this.objects = []; //属于当前节点的对象,不包括子节点的对象 this.nodes = []; //属于当前树的节点 }; 划分 当调用Insert函数向树中插入对象的时候,如果当前节点没有被划分过的时候,会判断节点的对象数是否超过的限制的max_objects,如果超过了的话当前节点就会调用这个split方法。 Quadtree.prototype.split = function() { var nextLevel = this.level + 1; var subWidth = Math.round( this.bounds.width / 2 ); var subHeight = Math.round( this.bounds.height / 2 ); var x = Math.round( this.bounds.x ); var y = Math.round( this.bounds.y ); //第一象限,和数学里的坐标轴一样,不过起点变了而已 this.nodes[0] = new Quadtree({ x : x + subWidth, y : y, width : subWidth, height : subHeight }, this.max_objects, this.max_levels, nextLevel); //第二象限 this.nodes[1] = new Quadtree({ x : x, y : y, width : subWidth, height : subHeight }, this.max_objects, this.max_levels, nextLevel); //第三象限 this.nodes[2] = new Quadtree({ x : x, y : y + subHeight, width : subWidth, height : subHeight }, this.max_objects, this.max_levels, nextLevel); //第四象限 this.nodes[3] = new Quadtree({ x : x + subWidth, y : y + subHeight, width : subWidth, height : subHeight }, this.max_objects, this.max_levels, nextLevel); }; ...

December 1, 2021 · 3 min · LiuYingbo

GitHub+Hexo 搭建个人博客(四):SEO 优化及站点被搜索引擎收录设置

前言 我们必须把我们的网站推送到搜索引擎那, 不然别人除了输入我们的域名或者搜索文章,是没法发现我们的博文。 如何查看我的网站是否被收录: site:你的网站 比如我的:site:liuyingbo.com 站点地图 站点地图即 sitemap, 是一个页面,上面放置了网站上需要搜索引擎抓取的所有页面的链接。站点地图可以告诉搜索引擎网站上有哪些可供抓取的网页,以便搜索引擎可以更加智能地抓取网站。所以我们首先需要生成一个站点地图 安装百度和 Google 的站点地图生成插件: npm install hexo-generator-baidu-sitemap --save npm install hexo-generator-sitemap --save 然后来到站点目录配置文件_config.yml,在下面添加: # 站点地图 sitemap: path: sitemap.xml baidusitemap: path: baidusitemap.xml 然后重新推送到服务器,在访问如下 URL: https://你的域名/sitemap.xml https://你的域名/baidusitemap.xml 看看有没有出现代码。有的话就成功。 给你的 hexo 网站添加蜘蛛协议 robots.txt, 把 robots.txt 放在你的 hexo 站点的 source 文件下即可。 # hexo robots.txt User-agent: * Allow: / Sitemap: https://liuyyingbo.com/sitemap.xml Sitemap: https://liuyingbo.com/baidusitemap.xml 百度收录 提交网站 通过百度站长平台进行链接提交,增加网站的索引量。先去注册并登录:百度站长平台 然后需要验证网站,我选择的是https://,这根据你前面是否添加 SSL 证书来选择。并且我使用的是不带 www 的,看个人。然后到第三步,我使用的 HTML 标签验证。你也可以选择自己喜欢的方式 ...

November 30, 2021 · 2 min · LiuYingbo

GitHub+Hexo 搭建个人博客(三):使用 GitHub Actions 实现 Hexo 博客自动部署

Hexo 相关知识点 静态博客简单,但是发布博文时稍显麻烦,一般需要下面两步: hexo clean hexo g -d // 相当于 hexo g + hexo d 如果考虑到同步源文件,还需要每次更改后,将源文件 push 到指定仓库: git push origin main 我们可以将 Hexo 文件分为两类,一类是源文件,即下面这些文件: . ├── _config.yml ├── package.json ├── scaffolds ├── source | ├── _drafts | └── _posts └── themes 一类是 public 文件,即网站文件: public ├── 2020 ├── categories ├── tags .... 发布博文的这三个操作代表: hexo clean:删除网站(public)文件 hexo g:生成网站(public)文件 hexo d:将本地网站(public)文件同步到指定仓库(如:yourname.github.io)中 我使用一个私有仓库存放 Hexo 源文件,在 deppwang/deppwang.github.io 中存放网站文件。所以每次发布或者更新博文时,需要使用 push 操作更新源文件,再执行 hexo clean、hexo g -d 更新博客,比较麻烦,而且github在国内经常登录不上。 ...

November 30, 2021 · 2 min · LiuYingbo