AchannelisadeliveryroutesuchasSMS or mail. Decidingwhether to add an urgent prefix is a notice rule;delivering the prepared messageisthe channelβsjob.Bridgeseparatesthese independently changing roles and connects a notice object to a channel object.
Content blocks 5 β 5
The text is unchanged, but footnotes, formatting or block attributes have changed.
Changingtheurgentwording should not requireeditingbothSMSandmail implementations. Adding pushdeliveryshouldnotrequire rewriting the normalandurgent rules either. NoticeusesonlyIChannel,thesharedcontract for passing a message to a channel, so the two responsibilities can change separately.
Content blocks 14 β 14
β
using System;
using System.Collections.Generic;
using System.Linq;
Console.WriteLine(new NormalNotice(new SmsChannel()).Send("ready"));
interface IChannel { string Deliver(string message); }
sealed class SmsChannel : IChannel
{
public string Deliver(string message) => "SMS: " + message;
}
sealed class MailChannel : IChannel
{
public string Deliver(string message) => "MAIL: " + message;
}
abstract class Notice(IChannel channel)
{
protected IChannel Channel { get; } = channel;
public abstract string Send(string message);
}
sealed class NormalNotice(IChannel channel) : Notice(channel)
{
public override string Send(string message) => Channel.Deliver(message);
}
sealed class UrgentNotice(IChannel channel) : Notice(channel)
{
public override string Send(string message) => Channel.Deliver("URGENT " + message);
}
+
using System;
using System.Collections.Generic;
using System.Linq;
Console.WriteLine(new NormalNotice(new SmsChannel()).Send("ready"));
// Notices pass messages through this contract without knowing an SMS or mail API.
interface IChannel { string Deliver(string message); }
// Return the selected channel name instead of sending an actual message.
sealed class SmsChannel : IChannel
{
public string Deliver(string message) => "SMS: " + message;
}
sealed class MailChannel : IChannel
{
public string Deliver(string message) => "MAIL: " + message;
}
abstract class Notice(IChannel channel)
{
// Keep the supplied channel. The constructor connects notice kind and delivery.
protected IChannel Channel { get; } = channel;
public abstract string Send(string message);
}
sealed class NormalNotice(IChannel channel) : Notice(channel)
{
public override string Send(string message) => Channel.Deliver(message);
}
sealed class UrgentNotice(IChannel channel) : Notice(channel)
{
// The urgent prefix is a notice rule, so add it here instead of in every channel.
public override string Send(string message) => Channel.Deliver("URGENT " + message);
}
In new NormalNotice(newSmsChannel()),the outer object selects the notice kind and the inner object selects its delivery route. NormalNotice passes the message to IChannel.Deliver() ratherthancalling an SMS API directly.
Content blocks 19 β 19
β
using System;
using System.Collections.Generic;
using System.Linq;
foreach (IChannel channel in new IChannel[] { new SmsChannel(), new MailChannel() })
{
Console.WriteLine(new NormalNotice(channel).Send("ready"));
Console.WriteLine(new UrgentNotice(channel).Send("ready"));
}
interface IChannel { string Deliver(string message); }
sealed class SmsChannel : IChannel
{
public string Deliver(string message) => "SMS: " + message;
}
sealed class MailChannel : IChannel
{
public string Deliver(string message) => "MAIL: " + message;
}
abstract class Notice(IChannel channel)
{
protected IChannel Channel { get; } = channel;
public abstract string Send(string message);
}
sealed class NormalNotice(IChannel channel) : Notice(channel)
{
public override string Send(string message) => Channel.Deliver(message);
}
sealed class UrgentNotice(IChannel channel) : Notice(channel)
{
public override string Send(string message) => Channel.Deliver("URGENT " + message);
}
+
using System;
using System.Collections.Generic;
using System.Linq;
// Reuse the same notice kinds while replacing only the channel.
foreach (IChannel channel in new IChannel[] { new SmsChannel(), new MailChannel() })
{
Console.WriteLine(new NormalNotice(channel).Send("ready"));
Console.WriteLine(new UrgentNotice(channel).Send("ready"));
}
// Notices pass messages through this contract without knowing an SMS or mail API.
interface IChannel { string Deliver(string message); }
// Return the selected channel name instead of sending an actual message.
sealed class SmsChannel : IChannel
{
public string Deliver(string message) => "SMS: " + message;
}
sealed class MailChannel : IChannel
{
public string Deliver(string message) => "MAIL: " + message;
}
abstract class Notice(IChannel channel)
{
// Keep the supplied channel. The constructor connects notice kind and delivery.
protected IChannel Channel { get; } = channel;
public abstract string Send(string message);
}
sealed class NormalNotice(IChannel channel) : Notice(channel)
{
public override string Send(string message) => Channel.Deliver(message);
}
sealed class UrgentNotice(IChannel channel) : Notice(channel)
{
// The urgent prefix is a notice rule, so add it here instead of in every channel.
public override string Send(string message) => Channel.Deliver("URGENT " + message);
}
Content blocks none β 22-26
β
(No content)
+
TypeScript example
The TypeScript version separates the same roles. Send and Deliver in the UML correspond to send and deliver below; the constructor still connects a notice to its channel.
// Notices pass messages through this contract without knowing an SMS or mail API.
interface IChannel {
deliver(message: string): string;
}
// Return the selected channel name instead of sending an actual message.
class SmsChannel implements IChannel {
deliver(message: string): string { return "SMS: " + message; }
}
class MailChannel implements IChannel {
deliver(message: string): string { return "MAIL: " + message; }
}
abstract class Notice {
protected readonly channel: IChannel;
constructor(channel: IChannel) {
// Keep the supplied channel. The constructor connects notice kind and delivery.
this.channel = channel;
}
abstract send(message: string): string;
}
class NormalNotice extends Notice {
override send(message: string): string {
return this.channel.deliver(message);
}
}
class UrgentNotice extends Notice {
override send(message: string): string {
// The urgent prefix is a notice rule, so add it here instead of in every channel.
return this.channel.deliver("URGENT " + message);
}
}
// Reuse the same notice kinds while replacing only the channel.
for (const channel of [new SmsChannel(), new MailChannel()]) {
console.log(new NormalNotice(channel).send("ready"));
console.log(new UrgentNotice(channel).send("ready"));
}
SMS: ready
SMS: URGENT ready
MAIL: ready
MAIL: URGENT ready
A new channel that implements deliver() works with both NormalNotice and UrgentNotice. Adding a notice kind likewise leaves the existing channels unchanged.
Content blocks 24 β 29
β
Each example is a separate .NET 10 console program.
+
Each C# example is a separate .NET 10 console program. Compile the TypeScript example to JavaScript and run it with Node.js.