【读书活动感悟分享】极客充电队——《HeadFirst设计模式》剩余模式、组合应用、实践方式_文章

【读书活动感悟分享】极客充电队——《HeadFirst设计模式》剩余模式、组合应用、实践方式

刘洋
发表于 2025-11-08 13:57:45

一、剩余设计模式

介绍设计模式,尽量使用我们常见的系统类库来举例,方便带入理解


1.1 桥接模式

定义:类存在多维度独立变化,应采用组合方式,而非继承方式,防止子类爆炸

何为类爆炸:当一个Shape存在形状、颜色变化时,如果只用子类继承方式实现,则会导致实现类过多


public abstract class Shape { public virtual void Print() { Console.WriteLine("I'm Shape"); } }
public class Circle : Shape { public override void Print() { Console.WriteLine("I'm Circle"); } }
public class Square : Shape { public override void Print() { Console.WriteLine("I'm Square"); } }
public class RedCircle : Circle
{
   public override void Print()
  {
       Console.WriteLine("I'm Red Circle");
  }
}
public class BlueCircle : Circle
{
   public override void Print()
  {
       Console.WriteLine("I'm Blue Circle");
  }
}
public class RedSquare : Square
{
   public override void Print()
  {
       Console.WriteLine("I'm Red Square");
  }
}
public class BlueSquare : Square
{
   public override void Print()
  {
       Console.WriteLine("I'm Blue Square");
  }
}


桥接模式:

public interface IColor { string GetColor(); }
public class RedColor : IColor { public string GetColor() { return "Red"; } }
public class BlueColor : IColor { public string GetColor() { return "Red"; } }
public abstract class ShapeV2
{
   public IColor _color { get; set; }
   public ShapeV2(IColor color) { _color = color; }
   public virtual void Print() { Console.WriteLine($"I'm {_color.GetColor()} Shape"); }
}
public class Circle2 : ShapeV2
{
   public Circle2(IColor color) : base(color) { }
   public override void Print() { Console.WriteLine($"I'm {_color.GetColor()} Circle"); }
}
public class Square2 : ShapeV2
{
   public Square2(IColor color) : base(color) { }
   public override void Print() { Console.WriteLine($"I'm {_color.GetColor()} Square"); }
}


系统类库举例(代码有缩减):

public class BinaryReader : IDisposable
{
   private readonly Stream _stream;
   public BinaryReader(Stream input, Encoding encoding, bool leaveOpen)
  {
       _stream = input;
  }
   public virtual int Read(byte[] buffer, int index, int count)
  {
       return _stream.Read(buffer, index, count);
  }
}

public class StreamReader : TextReader
{
   private readonly Stream _stream;
   public StreamReader(Stream stream, Encoding? encoding = null, bool detectEncodingFromByteOrderMarks = true, int bufferSize = -1, bool leaveOpen = false)
  {
       _stream = stream;
  }
   public override string ReadToEnd()
  {
       StringBuilder sb = new StringBuilder(_charLen - _charPos);
       do
      {
           sb.Append(_charBuffer, _charPos, _charLen - _charPos);
           _charPos = _charLen;  // Note we consumed these characters
           ReadBuffer();
      } while (_charLen > 0);
       return sb.ToString();
  }
}


​1.2 生成器模式

定义:分步创建复杂对象,可根据配置动态构建

使用注意事项:无固定的代码结构,可以从IBuilder接口扩展多个实现类组合,主要在于形式上的Build过程,最后可调用方法获取结果,也可以在每个步骤内实现


public class Houser
{
   public string Make厕所()
  {
       return new RoomBuilder().Add马桶().Add水龙头().Build();
  }

   public string Make卧室()
  {
       return new RoomBuilder().Add床().Add衣柜().Build();
  }
}

public class RoomBuilder
{
   private StringBuilder _sb = new StringBuilder();
   public RoomBuilder Add马桶() { _sb.Append("马桶 "); return this; }
   public RoomBuilder Add水龙头() { _sb.Append("水龙头 "); return this; }
   public RoomBuilder Add床() { _sb.Append("床 "); return this; }
   public RoomBuilder Add衣柜() { _sb.Append("衣柜 "); return this; }
   public string Build() { return _sb.ToString(); }
}


系统类库举例(代码有缩减):

var uriBuilder = new UriBuilder {
   Scheme = "https",
   Host = "example.com",
   Port = 8080,
   Path = "/api/user",
   Query = "id=123&name=test"
};
Uri finalUri = uriBuilder.Uri;

var sb = new StringBuilder();
sb.Append("Hello, ");
sb.Append("World!");
sb.AppendLine();
sb.AppendFormat("Today is {0}.", DateTime.Now);
string result = sb.ToString();

var builder = new ConfigurationBuilder()
  .SetBasePath(Directory.GetCurrentDirectory())
  .AddJsonFile("appsettings.json")
  .AddEnvironmentVariables()
  .AddCommandLine(args);
IConfiguration config = builder.Build();


1.3 责任链模式

定义:对于输入的信息,构建一个处理链条,可以按照联调顺序,逐步处理自己部分的信息,也可以在某个步骤中断执行

特征:自身指向下一链条,并在自身方法执行时调用下一链条方法

public abstract class Filter
{
   private Filter _next;
   public Filter(Filter next)
  {
       _next = next;
  }
   public virtual void Validate(string text)
  {
       if (_next != null)
           _next.Validate(text);
  }
}

public class SafeFilter : Filter
{
   public SafeFilter(Filter next) : base(next) { }
   public override void Validate(string text)
  {
       if (text.Contains("#"))
           throw new Exception();
       base.Validate(text);
  }
}

public class NullFilter: Filter
{
   public NullFilter(Filter next) : base(next) { }
   public override void Validate(string text)
  {
       if (text == null)
           throw new Exception();
       base.Validate(text);
  }
}

public class Runner
{
   public void Run()
  {
       var filter = new SafeFilter(new NullFilter(null));
       filter.Validate("11111");
  }
}


系统类库举例(代码有缩减):

public delegate Task RequestDelegate(HttpContext context);

public static class LoggingMiddleware
{
   public static RequestDelegate UseLogging(RequestDelegate next)
  {
       return async context =>
      {
           Console.WriteLine($"[Logging] Request to {context.Request.Path}");
           await next(context);
           Console.WriteLine($"[Logging] Response: {context.Response.StatusCode}");
      };
  }
}
public static class AuthenticationMiddleware
{
   public static RequestDelegate UseAuthentication(RequestDelegate next)
  {
       return async context =>
      {
           if (context.Headers.TryGetValue("Authorization", out var token) && token == "valid-token")
          {
               Console.WriteLine("[Auth] User authenticated");
               await next(context);
          }
           else
          {
               Console.WriteLine("[Auth] Unauthorized: Missing or invalid token");
               context.Response.StatusCode = 401;
               context.Response.Body = "Unauthorized";
          }
      };
  }
}
public static class ResponseMiddleware
{
   public static RequestDelegate UseResponse(RequestDelegate next)
  {
       return async context =>
      {
           if (string.IsNullOrEmpty(context.Response.Body))
          {
               context.Response.Body = $"Hello from {context.Request.Path}";
          }
           Console.WriteLine("[Response] Sending response");
      };
  }
}
public static class MiddlewarePipeline
{
   public static RequestDelegate BuildPipeline()
  {
       RequestDelegate next = ResponseMiddleware.UseResponse;
       next = AuthenticationMiddleware.UseAuthentication(next);
       next = LoggingMiddleware.UseLogging(next);
       return next;
  }
}

public class Program
{
   public static async Task Main(string[] args)
  {
       var pipeline = MiddlewarePipeline.BuildPipeline();
       var validRequest = new HttpContext
      {
           Request = new HttpRequest { Path = "/home" },
           Headers = new Dictionary<string, string> { ["Authorization"] = "valid-token" }
      };
       Console.WriteLine("=== Testing Valid Request ===");
       await pipeline(validRequest);
       Console.WriteLine($"Result: {validRequest.Response.StatusCode}");
  }
}


1.4 享元模式

定义:对于相同属性对象,实际创建一个,所有使用者都共用同一个实例,减少内存消耗

要求:享元对象不可改变,如需改变创建新的并进行赋值

//正常创建树木
public class Tree
{
   public int x { get; set; }
   public int y { get; set; }
   public string Image { get; set; }
   public void Display()
  {
       Console.WriteLine($"在{x},{y}位置显示{Image}");
  }
}
public class TreeManager
{
   public List<Tree> trees = new List<Tree>();
   public void DisplayTrees()
  {
       foreach (var tree in trees)
           tree.Display();
  }
}

//享元模式
public class Tree
{
   public static void Display(int x,int y,string image)
  {
       Console.WriteLine($"在{x},{y}位置显示{image}");
  }
}

public class TreeManager
{
   public void DisplayTrees()
  {
       Tree.Display(1, 2, "1.jpg");
       Tree.Display(1, 2, "1.jpg");
       Tree.Display(1, 2, "1.jpg");
  }
}


系统类库举例(代码有缩减):

// 字符串字面量自动驻留
string s1 = "hello";       // 首次创建,加入字符串池
string s2 = "hello";       // 直接引用池中的对象(与 s1 共享)

Console.WriteLine(ReferenceEquals(s1, s2)); // 输出:True(同一对象)

// 动态创建的字符串(未驻留)
string s3 = new string('h', 1) + "ello";   // 新建对象(不在池中)
string s4 = "hello";

Console.WriteLine(ReferenceEquals(s3, s4)); // 输出:False(s3 未驻留)

// 显式驻留 s3
string s5 = string.Intern(s3);             // 将 s3 加入池(或返回已存在的 "hello")
Console.WriteLine(ReferenceEquals(s5, s4)); // 输出:True(共享池中的对象)

扩展:DDD中的ValueObject(值对象)要求也是不可变,有变化需要创建新的对象

同时DDD中的各层需要做ACL隔离,组件化脱离外部变化,要求每个组件基于自身定义出入参,所以需要组件之间的参数转化


1.5 解释器模式

定义:一种特殊的设计模式,专门针对一类问题进行处理,主要用于定义一个语言的语法,然后构建一个专门解析语法的设计模式

举例代码:


说明:无固定编写语法,只要实现解析功能即可

系统类库举例(代码有缩减):

  • 正则表达式

  • SQLConnect的连接串解析

  • IBatis中的sql解析

  • Lambda表达式等

  • Json反序列化


1.6 访问者模式

定义:将算法与对象结构分离,不改变结构的情况下新增操作

关键点:

  • 针对较为复杂的对象结构

  • 需要针对对象结构添加一些操作扩展

  • 不要改变对象本身的结构关系

  • 访问者类熟悉对象结构

  • 调用方不熟悉对象结构


实例:如果每新增一个导出方式,就将原有元素添加方法处理,不符合开闭原则

public interface ExportVisitor
{
   string Visit(TextElement text);
   string Visit(ImageElement text);
   string Visit(TableElement text);
}
public interface DocumentElement
{
   string Accept(ExportVisitor visitor);
}
public class TextElement : DocumentElement
{
   public string GetText() { return "我是文本"; }
   public string Accept(ExportVisitor visitor)
  {
       return visitor.Visit(this);
  }
}
public class ImageElement : DocumentElement
{
   public string GetPath() { return "图片路径"; }
   public string Accept(ExportVisitor visitor)
  {
       return visitor.Visit(this);
  }
}

public class TableElement : DocumentElement
{
   public string[,] Content = { { "1","2"},{ "1","2"} };
   public string Accept(ExportVisitor visitor)
  {
       return visitor.Visit(this);
  }
}

public class Document
{
   private List<DocumentElement> elements = new List<DocumentElement>();
   public void AddElement(DocumentElement element) { elements.Add(element); }
   public string export(ExportVisitor vistor)
  {
       StringBuilder sb = new StringBuilder();
       foreach(var e in elements)
      {
           sb.AppendLine(e.Accept(vistor));
      }
       return sb.ToString();
  }
}

public class HtmlExportVisitor : ExportVisitor
{
   public string Visit(TextElement text)
  {
       return $"<p>{text.GetText()}</p>";
  }

   public string Visit(ImageElement text)
  {
       return $"<img src={text.GetPath()}/>";
  }

   public string Visit(TableElement text)
  {
       return
           $"<table>" +
           $"<tr>" +
           $"<td>{text.Content[0, 0]}</td><td>{text.Content[0, 1]}</td>" +
           $"</tr>" +
           $"<tr>" +
           $"<td>{text.Content[1, 0]}</td><td>{text.Content[1, 1]}</td>" +
           $"</tr>" +
           $"</table>";
  }
}

public class HtmlExportVisitorTest
{
   public static void Test()
  {
       Document doc = new Document();
       doc.AddElement(new TextElement());
       doc.AddElement(new ImageElement());
       doc.AddElement(new TableElement());

       var visitor = new HtmlExportVisitor();
       Console.WriteLine(doc.export(visitor));
  }
}


系统类库举例(代码有缩减):



1.7 原型模式

定义:从一个对象复制出一个新的对象

注意:深浅拷贝的问题

示例:

public class Student : ICloneable
{
   public string name { get; set; }
   public int age { get; set; }
   public object Clone()
  {
       return new Student()
      {
           name = this.name,
           age = this.age
      };
       //return JsonConvert.DeserializeObject<Student>(JsonConvert.SerializeObject(this));
  }
}

常用场景:

数据库查询出一批数据,需要对数据做处理,还需要使用原来的数据,则可以克隆一份备用,避免重新查询


1.8 备忘录模式

定义:类似游戏存盘,可恢复之前的状态,针对关键对象状态进行保存,便于恢复

场景:文本编辑器的ctrl+z\ctrl+y

系统类库举例(代码有缩减):

using (var graphics = Graphics.FromImage(new Bitmap(200, 200)))
{
   // 初始状态:无变换
   graphics.FillRectangle(Brushes.White, 0, 0, 200, 200);

   // 保存当前状态(生成备忘录:GraphicsState)
   GraphicsState stateBeforeTransform = graphics.Save();

   // 修改状态:平移坐标系
   graphics.TranslateTransform(50, 50);
   graphics.FillRectangle(Brushes.Red, 0, 0, 100, 100); // 在平移后的坐标系绘制

   // 恢复到保存的状态(使用备忘录)
   graphics.Restore(stateBeforeTransform);

   // 再次绘制(回到初始坐标系)
   graphics.FillRectangle(Brushes.Blue, 0, 0, 100, 100);
}


1.9 中介者模式

定义:通过一个中介层来简化调用者之间的相互调用关系

类比:MQ相互调用对比EventBus消息总线模式

MQ相互调用:发送者必须知道接收者是谁在哪,不同业务场景下还可能不同,每个发送者自己维护调用关系

EventBus消息总线:我只管发不关系接收方的信息维护,EventBus充当中介者来负责把消息转给需要的接受方

示例:

public class ChatUser
  {
       private ChatRoomMediator ChatRoom { get; set; }
       public string IPAddress { get; set; }
       public void ChatIn(ChatRoomMediator chatRoom)
      {
           ChatRoom = chatRoom;
      }
       public void SendMessage(string msg)
      {
           ChatRoom.PublishMsg(this, msg);
      }
       public void ReceiveMessage(string ip,string msg)
      {
           Console.WriteLine($"{ip}:{msg}");
      }
  }
   public class ChatRoomMediator
  {
       private List<ChatUser> users = new List<ChatUser>();

       public void PublishMsg(ChatUser user,string msg)
      {
           users.ForEach(x => x.ReceiveMessage(user.IPAddress, msg));
      }
  }

   public class ChatRoomMediatorTest
  {
       public static void Test()
      {
           var room = new ChatRoomMediator();
           var user1 = new ChatUser();
           var user2 = new ChatUser();
           user1.ChatIn(room);
           user2.ChatIn(room);
           user1.SendMessage("hello");
      }
  }


二、设计模式组合应用


2.1 组合模式应用

一个场景可以由多个设计模式组合使用来解决具体的问题

场景举例:

  • 不同的鸭子实现不同的呱呱叫(策略模式)

  • 把鹅叫也加入进来(适配器模式)

  • 统计鸭子叫声数量(装饰器模式)

  • 创建不同的鸭子(工厂模式)

  • 管理一群鸭子(迭代器模式)

  • 识别具体鸭子的呱呱叫行为(观察者模式)




2.2 组合模式之王-MVC

章节要点:

观察者模式

  • 模型作为被观察者,视图和控制器作为观察者注册监听

  • 当模型状态变化(如BPM值改变),自动通知所有观察者更新(如视图刷新界面)


策略模式

  • 视图将用户输入的处理委托给控制器(如controller.setBPM())

  • 控制器作为视图的策略,可灵活替换(例如DJ控制器替换为心跳控制器)


组合模式

  • 视图内部使用组合结构管理UI组件(如按钮、标签嵌套在面板中),以统一方式处理整体与部分的关系


个人感悟:

个人感觉这个章节的内容不是很好,只是体现思想,并不符合实际编码中对设计模式的使用。不过其实从此章节会延展出作者想表达的一点核心:设计模式是思想,并不局限于形式,而是面对实际问题用什么样的工程实践思想来解决问题


2.3 个人补充-Provider模式

提供者(Provider)模式,由微软提供,此模式由多种设计模式或设计思想组合而成

public interface ILogger
{
   void Log(string message);
}

public abstract class LoggerProviderBase : ProviderBase
{
   public abstract ILogger CreateLogger();
}
public class ConsoleLoggerProvider : LoggerProviderBase
{
   public override ILogger CreateLogger()
  {
       return new ConsoleLogger();
  }
}
public class ConsoleLogger : ILogger
{
   public void Log(string message)
  {
       Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [Console] {message}");
  }
}
public class FileLoggerProvider : LoggerProviderBase
{
   private readonly string _filePath;

   public FileLoggerProvider(string filePath)
  {
       _filePath = filePath ?? throw new ArgumentNullException(nameof(filePath));
  }

   public override ILogger CreateLogger()
  {
       return new FileLogger(_filePath);
  }
}
public class FileLogger : ILogger
{
   private readonly string _filePath;

   public FileLogger(string filePath)
  {
       _filePath = filePath;
       Directory.CreateDirectory(Path.GetDirectoryName(_filePath));
  }

   public void Log(string message)
  {
       File.AppendAllText(_filePath, $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [File] {message}{Environment.NewLine}");
  }
}
public class LoggerProviderConfiguration
{
   public string ProviderName { get; set; } = "Console";
   public string FilePath { get; set; } = "logs/app.log";
}

public class LoggerProviderFactory
{
   private readonly LoggerProviderConfiguration _config;
   public LoggerProviderFactory(IOptions<LoggerProviderConfiguration> configOptions)
  {
       _config = configOptions.Value;
  }

   public LoggerProviderBase CreateProvider()
  {
       return _config.ProviderName.ToLower() switch
      {
               "file" => new FileLoggerProvider(_config.FilePath),
               "console" => new ConsoleLoggerProvider(),
               _ => throw new ArgumentException($"不支持的日志提供者:{_config.ProviderName}")
      };
  }
}

static void Main(string[] args)
{
   var hostBuilder = new HostBuilder()
      .ConfigureAppConfiguration(
      (_, config) =>
      {
           config.AddJsonFile("appsettings.json", optional: false);
      })
      .ConfigureServices((context, services) =>
      {
           services.Configure<LoggerProviderConfiguration>(
               context.Configuration.GetSection("LoggerProvider")
          );

           services.AddSingleton<LoggerProviderFactory>();
           services.AddTransient<ILogger>(
               provider =>
              {
                   var factory = provider.GetRequiredService<LoggerProviderFactory>();
                   var loggerProvider = factory.CreateProvider();
                       return loggerProvider.CreateLogger();
                  });
              });

   using var host = hostBuilder.Build();
   var logger = host.Services.GetRequiredService<ILogger>();

   logger.Log("这是一条测试日志!");
   logger.Log("Provider 模式演示成功!");
   Console.ReadLine();
}

总结下:

  • 策略模式:多种Provider

  • 工厂模式:工厂根据配置创建具体的Provider

  • 解释器模式:appsettings.json解析

  • 桥接模式:Factory工厂与Configuration解绑

  • 单例模式:DI容器添加单例工厂、配置等

  • 创建者模式:Host进程创建由Builder模式构建

三、现实中的设计模式

3.1 书中观点

设计模式定义:模式是在某个上下文中针对某个问题的解决方案

  • 上下文:模式的适用场景,并且此种场景会不断的出现

  • 问题:代之你要解决的目标

  • 解决方案:一种通用的设计规范,大部分人都能快速认同理解,并选择相同的方案


举例:

  • 问题:我怎么能准时上班?

  • 上下文:我把钥匙丢在车内了

  • 解决方案:打破窗户,进入车内,开车上班


章节关键信息点:

  • 设计模式不是法律或者准则,只是指导方针,可以修改写法去适应你的问题

  • 可以创造设计模式吗?模式是被发现的,不是被创建的,如果发现某个领域里重复出现一个问题,并被你使用一个方案解决,也可以认为是创造设计模式(可以找三个程序员,如果他们都认同,就是通用的设计模式方案)

  • 用模式思考问题:

    • 保持简单

    • 设计模式不是灵丹妙药,实际它什么都不是

    • 知道什么时候需要设计模式

    • 重构时间就是模式时间

    • 拿掉不需要的,不要害怕移除设计模式

    • 如果现在不需要就别做

  • 模式的心智:

    • 初学者单纯使用模式

    • 中阶者思考什么时候需要设计模式,什么时候不需要

    • 悟道者能够看到模式的自然融入

  • 反模式:一开始认为是好模式,但在长期维护后发现是一个坏模式,通过反模式归档,帮助其他人借鉴避坑

3.2 个人观点

  • 基于当下做决策,不要依赖自己的想象去构建代码

  • 技术类的组件,多用设计模式,因为其特点是你发明的,你可以清楚的知道它的使用模式和形态

  • 业务需求类的,尽量减少设计模式的使用,因为其特征是异变的,可能需求的提出者都无法料到下次修改的点是什么

  • 设计模式的作用是把一个组件做好,我们日常面对的问题是如何定义这个组件,所以如果业务能够做到标准产品化,可以基于设计模式来构建业务逻辑

  • 局部使用,不要针对大的流程节点设计模式,细节多异变,更多的设计组件+衔接运转,不要把整个流程拿来做抽象

  • 学习设计模式最好的方式就是阅读源码





379 0

评论


意见反馈