Decorator Pattern (装饰模式)

装饰模式可「动态」地给一个对象添加一些额外的职责,提供有别于「继承」的另一种选择。就扩展功能而言,Decorator Pattern 透过 Aggregation (聚合) 的特殊应用,降低了类与类之间的耦合度,会比单独使用「继承」生成子类更为灵活。

一般用「继承」来设计子类的做法,会让程序变得较僵硬,其对象的行为,是在「编译」时期就已经「静态」决定的,而且所有的子类,都会继承到相同的行为;然而,若用「装饰模式」以及 UML 的 Aggregation 的设计,来扩展对象的行为,就能弹性地 (flexible) 将多个「装饰者」混合着搭配使用,而且是在「执行」时期「动态」地进行扩展。

此外,若用一般「继承」的做法,每当对象需要新行为时,必须修改既有的代码、重新编译;但若透过「装饰模式」,则无须修改既有代码。

The Decorator Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
                                 - Design Patterns: Elements of Reusable Object-Oriented Software


图 1 此图为 Decorator 模式的经典 Class Diagram

01_Shell / Program.cs
using System;

namespace _01_Shell
{
    //客户端程序
    class Program
    {
        static void Main(string[] args)
        {
            ConcreteComponent c = new ConcreteComponent();
            ConcreteDecoratorA d1 = new ConcreteDecoratorA();
            ConcreteDecoratorB d2 = new ConcreteDecoratorB();

//让 ConcreteDecorator 在运行时,动态地给 ConcreteComponent 增加职责
            d1.SetComponent(c);
            d2.SetComponent(d1);
            d2.Operation();

Console.Read();
        }
    }

//装饰者、被装饰者的共同基类
    abstract class Component
    {
        public abstract void Operation();
    }

//被装饰者。具体被装饰对象。
    //此为在「执行」时期,会被 ConcreteDecorator 具体装饰者,「动态」添加新行为(职责)的对象
    class ConcreteComponent : Component
    {
        public override void Operation()
        {
            Console.WriteLine("具体被装饰对象的操作");
        }
    }

//装饰者。
    //此为所有装饰者,应共同实现的抽象类或接口
    abstract class Decorator : Component
    {
        //以父类声明的字段。
        //实现 UML Class Diagram 的 Aggregation,指向 Component 对象的指针。
        protected Component component;

public void SetComponent(Component component)
        {
            this.component = component;
        }

public override void Operation()
        {
            if (component != null)
            {
                component.Operation();
            }
        }
    }

//具体装饰者 A
    //此为在「执行」时期,替 ConcreteComponent「动态」添加新行为(职责)的对象
    class ConcreteDecoratorA : Decorator
    {
        //装饰者可以添加新的栏位
        private string addedState;

public override void Operation()
        {
            base.Operation();
            addedState = "New State";
            Console.WriteLine("具体装饰对象 A 的操作");
        }
    }

//具体装饰者 B
    //此为在「执行」时期,替 ConcreteComponent「动态」添加新行为(职责)的对象
    class ConcreteDecoratorB : Decorator
    {
        public override void Operation()
        {
            base.Operation();
            AddedBehavior();
            Console.WriteLine("具体装饰对象 B 的操作");
        }

//装饰者可以添加新的方法
        private void AddedBehavior()
        {
        }
    }

} // end of namespace

/*
执行结果:

具体被装饰对象的操作
具体装饰对象 A 的操作
具体装饰对象 B 的操作

*/

上方图 1 的 Class Diagram,以及「Shell (壳)」示例中,ConcreteComponent 即为此一模式中的「被装饰者」,而 Decorator 抽象类及其具体子类 ConcreteDecoratorA、ConcreteDecoratorB 即为「装饰者」。此外,这个模式中最核心的就是 Decorator 这个抽象类,它用一个以父类 Component 声明的变量 component,实现 UML 中的 Aggregation (聚合,有的博文也统称为「组合」或「合成」),亦即为指向 Component 对象的指针,达到我们前述 - 「动态」进行扩展的目的。

至于其设计的原理,以下我引用博客园一位前辈 Justin 两年前所写博文的一段内容 [1],这段是我认为对 Decorator 模式极优的说明。读者亦可搭配参考上方代码里我添加的注释,稍后我会再补充说明。

--------------------------------------------------------

Decorator 是装饰者模式里非常特殊的一个类,它既继承于 Component【IS A关系】,又维护一个指向 Component 实例的引用【HAS A关系】,换个角度来说,Decorator 跟 Component 之间,既有动态组合关系,又有静态继承关系,WHY? 这里为什么要这么来设计?上面我们说过,组合的好处是可以在运行时给对象增加职责,Decorator【HAS A】Component 的目的,是让 ConcreteDecorator 可以在运行时动态地给 ConcreteComponent 增加职责,这一点相对来说还比较好理解;那么 Decorator 继承于 Component 的目的是什么?在这里继承的目的只有一个,那就是可以统一「装饰者」和「被装饰者」的接口,换个角度来说,不管是 ConcretComponent 还是 ConcreteDecorator,它们都是最顶层 Component 基类,用户代码可以把它们统一看作 Component 来处理,这样带来的更深一层好处就是,「装饰者」对象,对「被装饰者」对象的功能职责扩展,对用户代码来说是完全透明的,因为用户代码引用的都是 Component,所以就不会因为「被装饰者」对象在被装饰后,引用它的用户代码发生错误,实际上不会有任何影响,因为装饰前后,用户代码引用的都是 Component 类型的对象,这真是太完美了!「装饰模式」通过继承,实现统一了「装饰者」和「被装饰者」的接口,通过组合获得了在运行时动态扩展「被装饰者」对象的能力。

我们再举个生活中的例子,俗话说“人在衣着马在鞍”,把这用「装饰模式」的语境翻译一下,“人通过漂亮的衣服装饰后,男人变帅了,女人变漂亮了;”。对应上面的类图,这里「人」对应于 ConcreteComponent,而「漂亮衣服」则对应于 ConcreteDecorator;换个角度来说,人和漂亮衣服组合在一起【HAS A】,有了帅哥或美女,但是他们还是人【IS A】,还要做人该做的事情,但是可能会对异性更有吸引力了(扩展功能)!

--------------------------------------------------------

上方 Justin 前辈,其文章的「煮咖啡」示例 [1],是引用自 O'Reilly 出版社的「Head First 设计模式」这本书的第三章 [10]。该文的煮咖啡类图中,HouseBlend (家常咖啡)、DarkRoast (深度烘培咖啡)、Espresso (意大利特浓咖啡)、Decaf (无咖啡因咖啡),这四种咖啡 (饮料),即为「被装饰者」,等同本帖上图 1 中的 ConcreteComponent 类;该文类图中的 CondimentDecorator 抽象类,等同本帖上图 1 中最重要的 Decorator 抽象类,亦即「装饰者」的抽象定义;该文类图中的 Milk、Mocha、Soy、Whip 这四种调料 (调味品),即为具体的「装饰者」,亦即在本帖一开始提到,这四种调料,可弹性地 (flexible) 混合着搭配使用,而且是在「执行」时期「动态」地进行扩展,亦即动态地装饰 HouseBlend、DarkRoas、Espresso、Decaf 这四种咖啡。

接下来,我们用另一个简单的例子来实现 Decorator 模式,并改用 ASP.NET 网页程序来实现。类图及代码如下方图 2 所示,执行结果如下图 3 所示。

此为一个西餐牛排馆的计费程序,这间牛排馆有两种主菜 - 牛排和猪排,此为「被装饰者」;有四种副菜 - 面条、生菜沙拉、饮料 (热饮或冷饮)、甜点,此为「装饰者」。客人点餐时,可点一种主菜,副菜可点一份、可点多份,也可不点 (有弹性地将多个「装饰者」混合着搭配);每样主菜和副菜都有各自的价格,全部相加后,即为一或多位客人的用餐费用。而且我们希望,将来这个计费程序写好后,未来即使要修改价格,或添加新的主菜和副菜时,都不用再修改既有的程序。


图 2 示例 02_Steak.aspx.cs 的 Class Diagram

02_Steak.aspx.cs
using System;
using com.cnblogs.WizardWu.sample02;       //客戶端程序调用服务器端的组件和类

//客戶端程序
public partial class _02_Steak : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        //点一客猪排,不需要副菜。并列出它的描述与价格
        Meal meal1 = new PorkChop();
        //Meal meal1 = new BeefSteak();
        Response.Write(meal1.Description + " = $ " + meal1.cost() + "
");

//点一客猪排,副菜只要面条。并列出它的描述与价格
        Meal meal2 = new PorkChop();
        meal2 = new Noodle(meal2);          //用 Noodle 装饰它 (运行时动态地增加职责)
        Response.Write(meal2.Description + " = $ " + meal2.cost() + "
");

//点一客猪排,因为这个人食量大,副菜要两份面条、一杯饮料(热饮)、一盘甜点。并列出它的描述与价格
        Meal meal3 = new PorkChop();
        meal3 = new Noodle(meal3);          //用 Noodle 装饰它
        meal3 = new Noodle(meal3);          //用第二个 Noodle 装饰它
        meal3 = new Drink(meal3, true);    //用 Drink 装饰它
        meal3 = new Dessert(meal3);         //用 Dessert 装饰它
        Response.Write(meal3.Description + " = $ " + meal3.cost() + "
");

//第四个人不吃猪肉,因此主菜改点一客牛排,副菜要一盘沙拉、一杯饮料(冷饮)。并列出它的描述与价格
        Meal meal4 = new BeefSteak();
        meal4 = new Salad(meal4);            //用 Salad 装饰它 (运行时动态地增加职责)
        meal4 = new Drink(meal4, false);   //用 Drink 装饰它
        Response.Write(meal4.Description + " = $ " + meal4.cost() + "
");
    }
}

//服務器端程序
namespace com.cnblogs.WizardWu.sample02
{
    //装饰者(副菜)、被装饰者(主菜)的共同基类。类似前一例的 Component 抽象类
    abstract class Meal
    {
        protected string description = "西餐";

public virtual string Description
        {
            get { return description; }
            set { description = value; }
        }

abstract public int cost();        //必须在子类实作的抽象方法,除非该个子类也是抽象类
    }

//主菜(被装饰者)。类似前一例的 ConcreteComponent 类
    class PorkChop : Meal                //主菜 - 猪排。以后执行时,才会动态被副菜装饰
    {
        public PorkChop()                  //构造函数
        {
            Description = "猪排";
        }

public override int cost()         //具体对象的操作
        {
            return 130;                        //猪排的价格。现在不需要管「副菜」的价格,直接返回猪排的价格即可
        }
    }

//主菜(被装饰者)。类似前一例的 ConcreteComponent 类
    class BeefSteak : Meal               //主菜 - 牛排。以后执行时,才会动态被副菜装饰
    {
        public BeefSteak()                 //构造函数
        {
            Description = "牛排";
        }

public override int cost()         //具体对象的操作
        {
            return 150;                        //牛排的价格。现在不需要管「副菜」的价格,直接返回牛排的价格即可
        }
    }

//装饰者。类似前一例的 Decorator 抽象类。此为所有「副菜(装饰者)」的父类。
    //功能一、未来所有可能添加的副菜,其共同的属性、行为,都可放在这个类里。
    //功能二、用来「分类」哪些是主菜,哪些是副菜。
    abstract class CondimentDecorator : Meal      //副菜,用来「动态」装饰主菜和 Meal 基类,亦即在运行时动态地替他们增加职责
    {
        //以父类声明的字段。
        //实现 UML Class Diagram 的 Aggregation,指向 Meal 对象的指针。
        protected Meal meal;

public override string Description
        {
            get { return meal.Description + " + " + description; }
            set { description = value; }
        }
    }

//副菜(装饰者)。具体装饰类 - 面条,用来动态装饰主菜 - 牛排或猪排
    class Noodle : CondimentDecorator            //类似前一例的 ConcreteDecoratorA
    {
        public Noodle(Meal meal)        //构造函数
        {
            Description = "面条";
            this.meal = meal;
        }

public override int cost()         //具体装饰对象 A 的操作
        {
            return 50 + meal.cost();
        }
    }

//副菜(装饰者)。具体装饰类 - 沙拉,用来动态装饰主菜 - 牛排或猪排
    class Salad : CondimentDecorator             //类似前一例的 ConcreteDecoratorB
    {
        public Salad(Meal meal)          //构造函数
        {
            Description = "生菜沙拉";
            this.meal = meal;
        }

public override int cost()         //具体装饰对象 B 的操作
        {
            return 60 + meal.cost();
        }
    }

//副菜(装饰者)。具体装饰类 - 饮料,用来动态装饰主菜 - 牛排或猪排
    class Drink : CondimentDecorator             //类似前一例的 ConcreteDecoratorC
    {
        private bool isHot;

public bool IsHot
        {
            get { return isHot; }
        }

public Drink(Meal meal, bool isHot)          //构造函数
        {
            Description = (isHot) ? "热饮" : "冷饮";
            this.meal = meal;
            this.isHot = isHot;
        }

public override int cost()         //具体装饰对象 C 的操作。冷饮比热饮多收 20 块钱
        {
            return (isHot) ? (30 + meal.cost()) : (50 + meal.cost());
        }
    }

//副菜(装饰者)。具体装饰类 - 甜点,用来动态装饰主菜 - 牛排或猪排
    class Dessert : CondimentDecorator          //类似前一例的 ConcreteDecoratorD
    {
        public Dessert(Meal meal)       //构造函数
        {
            Description = "甜点";
            this.meal = meal;
        }

public override int cost()         //具体装饰对象 D 的操作
        {
            return 40 + meal.cost();
        }
    }

} // end of namespace


图 3 示例 02_Steak.aspx.cs 的执行结果。扩展功能时,主菜和副菜可任意混合搭配使用

原理与本帖之前提过的相同,将来店里若要推出 羊排、鱼排、…等新的主菜 (被装饰者) 时,只要增加一个类,去继承 Meal 这个抽象的基类即可;若要推出新的副菜 (装饰者) 也一样,只要增加一个类,去继承 CondimentDecorator 抽象类即可。我们透过这个重要的 CondimentDecorator 抽象类,实现 UML 的 Aggregation (聚合) 观念,以「装饰模式」取代传统「继承」的做法,也同时降低了「主菜」、「副菜」这两种类之间的耦合度。

这个 CondimentDecorator 抽象类的作用,还可将未来所有种类的副菜,一些「共同的」行为或职责写在里面。而且,它还可用来区分哪些类是主菜,哪些类是副菜,因为主菜、副菜都是继承自 Meal 这个最顶层的基类,若没有这个 CondimentDecorator 抽象类,将来 Class Diagram 变得很复杂时,会不易分辨某个类是主菜或副菜。

此外,也因为主菜 (被装饰者)、副菜 (装饰者) 都是继承自 Meal 这个最顶层的基类,所以客户端程序 (Page_Load) 在引用他们的对象时,把 new 出来的主菜实例、new 出来的副菜实例,再抛入其他副菜实例的构造函数中,也会相容、不会发生错误,在相互引用和「运行」时期的「动态」扩展上,变得很有弹性。

最后补充一点,Decorator 模式中,装饰者类对象,在组合时的「顺序」亦很重要。本帖图 1 中,ConcreteDecoratorA、ConcreteDecoratorB、ConcreteDecoratorC、... 等具体装饰者,在动态添加和组合时,在某些实际应用中必须依照一定的顺序,若顺序相反可能会导致程序执行结果不合理,但本帖所提供的几个例子中并不需要考虑到这点。

--------------------------------------------------------

Decorator Pattern 适用的情景:

  • 你拥有一个已存在的组件类,却无法继承它 (subclassing)。
  • 能够动态地为对象添加职责 (添加状态和行为)。
  • 改变类中的成员和行为,但不影响其他对象。
  • 希望能便于职责的撤消。
  • 不想用「继承」来扩展行为。其中一种原因是避免当一些功能要交叉搭配引用时,单独用「继承」来设计会产生太多的子类、太复杂的类图结构 [1], [10];另一种考量可能是因为类的定义被隐藏,或类的定义不能用于生成子类。

Decorator Pattern 的优点:

  • 可避免单独使用「继承」时,在扩展时不够弹性,且可能衍生过多的子类。
  • 扩展时不需要修改既有的代码。
  • 可在执行时期,动态地添加新行为 (职责)。

Decorator Pattern 的缺点:

  • 可能会在程序中出现许多的小型类,亦即需要编写很多 ConcreteDecorator 类 (具体装饰者)。
  • 若过度使用 Decorator 模式,会让程序逻辑变得很复杂。
  • 别人较不易理解设计方式及代码,排查故障、追踪和调试也比较困难。

Decorator Pattern 的其他特性:

  • 每个要装饰的功能,都放在单独的类中。
  • 我们可以用无数个装饰者,去包装一个组件。
  • 「装饰者」可以在「被装饰者」前面或后面,添加自己的行为,甚至将「被装饰者」的行为整个取代掉,以达到特定的目的。
  • 「被装饰者」并不需要知道它已经被「装饰」过了,亦即 Component 类 (对象) 并不需要知道 Decorator 类 (对象) 的存在,且 Decorator 也仅仅认识 Component。

--------------------------------------------------------

最后再补充,在 Java I/O、.NET I/O、.NET System.Windows.Controls 命名空间中,很多地方都有用到 Decorator 模式来设计 [2], [5], [12]。例如我们查询 MSDN Library,会发现 .NET 3.x 版本的 Windows Forms 其 API 里,甚至还有增加一个新的 Decorator 类,如下:

System.Object
 System.Windows.Threading.DispatcherObject
  System.Windows.DependencyObject
   System.Windows.Media.Visual
    System.Windows.UIElement
     System.Windows.FrameworkElement
      System.Windows.Controls.Decorator
       System.Windows.Controls.Viewbox

System.Windows.Controls.Decorator 类:
提供在单个子元素(如 Border 或 Viewbox)上或周围应用效果的元素的基类。

http://msdn.microsoft.com/zh-cn/library/system.windows.controls.decorator.aspx
http://msdn.microsoft.com/zh-cn/library/system.windows.controls.decorator(VS.85).aspx

以下列出 Decorator Pattern,用于实际程序的四种方式 [12]:

  • 装饰模式非常适合于「图形程序」,亦适合于「视频和声音」,比如:视频流可用不同的比率进行压缩,而声音可以输入给同声传译服务。
  • 装饰模式如此有用,以致于 .NET 3.0 中,已经有实际的 Decorator 类了。如刚才提到的,在 System.Windows.Controls 命名空间中,提供了一些基类,可用于给其他的图形元素添加特殊效果,如:Border 类和 Viewbox 类。
  • 在如今的移动设备上,Web 浏览器及其他应用中,也大量运用 Decorator Pattern。比如这些设备上可以创建适合小屏幕显示的对象,跟标准桌面机上的浏览器相比,这些屏幕可能也含有滚动条,但不含有标题栏 [6]。

  • 从更一般的层次上,.NET 的 I/O API 中,到处都是装饰模式 [6],如下方的体系:
    System.Object
     System.MarshalByRefObject
      System.IO.Stream
       System.IO.BufferedStream
       System.IO.FileStream
       System.IO.MemoryStream
       System.Net.Sockets.NetworkStream
       System.Security.Cryptography.CryptoStream

上方体系的最后五个子类,有的就装饰了 Steram 这个父类 (如:BufferedStream 类),因为它们从它继承,同时又含有 Stream 类的实例,而且这个实例是子类被构造时传递进去的 (如同本帖第二个西餐店的示例中,副菜的构造函数的写法)。如下方 MSDN Library 代码的 BufferedStream 类,其构造函数的第一个参数,即为其父类 System.IO.Stream 的变量 stream。

BufferedStream 构造函数:

public BufferedStream(
 Stream stream
)

http://msdn.microsoft.com/zh-cn/library/system.io.bufferedstream.bufferedstream(VS.80).aspx

--------------------------------------------------------

本帖的最后,提供一位 Java 大师 - 结城浩,所绘制的 Decorator Pattern 趣味四格漫画 [9],原地址如下:

Giko 猫谈 DP 四格漫画:
http://www.hyuki.com/dp/cat_Decorator.html
http://www.javaworld.com.tw/jute/post/view?bid=44&id=40923&sty=3

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

∧_∧  敲敲敲  ╱
(    )  ∧ ∧ < System.IO.Stream 是个典型的例子..恩....。
(    )  (,,゚Д゚)  ╲____________
______ (つ_つ____
|   日∇ ╲|ThinkPad|╲
|      =========  ╲

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
____________

| 喔~、是 Decorator Pattern 吗?
╲ __ __________
  |╱
  ∧_∧       ╱
  ( ・∀・)  ∧ ∧ < 你你是谁? ...有..有什么事嬷你?・・・
  (  ⊃ )  (゚Д゚;)  ╲____________
________(つ_つ____
|    日∇ ╲|ThinkPad|╲
|       =========  ╲

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
__________

| 将 Concrete Decorator 重迭起来达到想要的目的..恩
╲ __ ________
  |╱
  ∧_∧       ╱
  ( ・∀・)  ∧ ∧ < 恩...恩恩..没错....
 (     )  (;゚Д゚)  ╲____________
_____ (つ_つ____
|     日∇╲|ThinkPad|╲
|       =========  ╲

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
__________

| 乱做一堆细微的类的人就是你.恩.
╲ __ ________
  |╱
 ∧_∧       ╱
 ( ・∀・)  ∧ ∧ < 不..不是我..!!
 (  ⊃ )  (゚Д゚;) ╲____________
_____(つ_つ____
|   日∇ ╲|ThinkPad|╲
|      =========  ╲
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

艾伟_转载:C# Design Patterns (3) - Decorator相关推荐

  1. 艾伟_转载:C# Design Patterns (4) - Proxy

    本帖介绍 Proxy Pattern (代理模式). Proxy Pattern (代理模式) The Proxy Pattern provides a surrogate or placeholde ...

  2. C# Design Patterns (3) - Decorator

    本帖介绍 Decorator Pattern (装饰模式),文章最后另提供 Decorator Pattern 的趣味四格漫画. ----------------------------------- ...

  3. 艾伟_转载:探索.Net中的委托

    废话 我本来以为委托很简单,本来只想简简单单的说说委托背后的东西,委托的使用方法.原本只想解释一下那句:委托是面向对象的.类型安全的函数指针.可没想到最后惹出一堆的事情来,越惹越多,罪过,罪过.本文后 ...

  4. 艾伟_转载:学习 ASP.NET MVC (第五回)理论篇

    本系列文章导航 学习 ASP.NET MVC (第一回)理论篇 学习 ASP.NET MVC (第二回)实战篇 学习 ASP.NET MVC (第三回)实战篇 学习 ASP.NET MVC (第四回) ...

  5. 艾伟_转载:C#语言基础常见问题汇总

    概述 1.什么是C#? C#是Microsoft公司设计的一种编程语言.它松散地基于C/C++,并且有很多方面和Java类似. Microsoft是这样描述C#的:"C#是从C和C++派生来 ...

  6. 艾伟_转载:.NET 4.0中数组的新增功能

    1.两数组是否"相等"? 在实际开发中,有时我们需要比对两个数组是否拥有一致的元素,例如,以下两个数组由于拥有相同的元素,因此被认为是相等的: int[] arr1 = new i ...

  7. 艾伟_转载:从ASP.NET的PHP执行速度比较谈起

    上星期我在InfoQ发表了一篇新闻,对Joe Stagner在博客上发表的三篇关于ASP.NET与PHP性能对比的文章进行了总结.写新闻其实挺不爽的,因为不能夹杂个人的看法,只能平铺直叙陈述事实.当然 ...

  8. 艾伟_转载:.NET设计模式:观察者模式(Observer Pattern)

    概述 在软件构建过程中,我们需要为某些对象建立一种"通知依赖关系" --一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知.如果这样的依赖关系过于紧密,将 ...

  9. 艾伟_转载:.NET内存管理、垃圾回收

    1. Stack和Heap 每个线程对应一个stack,线程创建的时候CLR为其创建这个stack,stack主要作用是记录函数的执行情况.值类型变量(函数的参数.局部变量 等非成员变量)都分配在st ...

最新文章

  1. Centos环境下实现DNS的智能解析
  2. Android 圆角Button
  3. java解压中文乱码_java使用解压zip文件,文件名乱码解决方案
  4. Apache TomEE(和Tomcat)的自签名证书
  5. 非法操作 login.php,阅文游戏中心 h5游戏接入wiki
  6. iOS给图片打水印,并将打过水印的图片生成到沙盒中
  7. Bzoj1007 [HNOI2008]水平可见直线
  8. flex布局怎么设置子元素大小_Web前端(三):前端布局
  9. 自己动手开发编译器(八)用Linq编写解析器组合子
  10. c#教程与视频教程类专题资料免费下载整理合集
  11. 金蝶KIS专业版本单据导入导出工具使用说明
  12. xxlJob 分布式定时任务
  13. 计算机怎么求标准偏差,如何计算Excel标准偏差?您必须知道这两个技巧
  14. vs2013设置winp#cap开发环境
  15. 【Unity入门】3.3D物体
  16. 性能测试和优化实录(mybatisplus异步写库@Async引起的对象无法释放问题)
  17. 更改分辨率时banner图片变形解决方案
  18. 嵌入式linux lvds,一种嵌入式主板实现双LVDS显示输出的电路及方法与流程
  19. 数据挖掘:数据(数据的基本统计描述)
  20. 关于AD前面的电压跟随器

热门文章

  1. C#中一些易混淆概念总结--------数据类型存储位置,方法调用,out和ref参数的使用...
  2. SFB 项目经验-12-为某上市企业的Skype for Business购买Godday证书
  3. 最最基本的Git入门 -- 本地仓库操作
  4. iOS开发中使用[[UIApplication sharedApplication] openURL:]加载其它应用
  5. 1-2 Zabbix web界面支持中文
  6. 4月份阿里云ECS和VPC升级公告
  7. Storm【配置项】 - 详细解释
  8. jQuery图片播放插件prettyPhoto使用介绍
  9. Nginx使用uninx socket来连接fastcgi(php)
  10. Javascript删除JSON元素