ほとんどの場合、私はいくつかのビジネスコードを作成しますが、CRUDの束で問題を解決できるかもしれませんが、この種の作業は技術者をあまり改善しません。ビジネスから自分を解放し、コードを書く楽しさを見つける方法です。いくつかの試みの後、ビジネスコードを改善するためにデザインパターンを使用することはそれらの1つです。
責任チェーンの設計パターン
定義
リクエストはチェーンで処理され、チェーンのアクセプターは、処理後に転送を続行するか、現在の処理フローを中断するかを決定します。
該当シーン
マルチノードプロセス処理に適しており、各ノードは独自の責任を果たし、OAの承認フローやJava Web開発のフィルターメカニズムなど、ノードは互いの存在を認識しません。人生の例を挙げると、家を借りているときに、いわゆる黒の仲介業者に会いました。家を借りたとき、私は神だと感じましたが、壊れたものを修理するように頼んだとき、私は孫のようでした。仲介業者は、カスタマーサービスの店を探すように頼みました。カスタマーサービスからまた家主を探すように言われ、家主から夫を探すように言われたのですが、結局問題は解決しました(家を借りる正規代理店を探す必要があります)。
経験
私が現在行っているビジネスは、キャンパスグループの食事の総支払いです。ビジネスプロセスは非常に簡単です。1。学生がモバイル支払いコードを開いて支払います。2。カフェテリアの叔母がマシンを使用して支払いコードをスキャンし、支払いを収集します。大学の食堂の背景はこんな感じで、食堂は助成金があり、料理も比較的安いので、学校は一般の人に食堂に行って食べさせたくないので、これを考慮して、支払う前に支払いが許可されているかどうかを確認する一連のロジックを追加しました。次のように:
-
特定のストールでは、特定のタイプのユーザーのみが消費できます。たとえば、教師のストールでは教師のみが消費でき、学生のストールでは外部ユーザーは消費できません。
-
特定の屋台では、特定のタイプのユーザーが1日に数回しか消費できません。たとえば、教師のカフェテリアでは、学生は1日に1回しか消費できません。
- 特定のハラルレストランなど、ハラル以外の学生が消費を許可されているかどうかにかかわらず、ハラル以外の学生は消費を許可されていません。
これらのタイプの状況に対して、私は3つのタイプのフィルターを確立しました。
SpecificCardUserConsumeLimitFilter:ユーザータイプに応じて消費を許可するかどうかを決定します
DayConsumeTimesConsumeLimitFilter:1日の消費数に応じて消費を許可するかどうかを決定します
MuslimConsumeLimitFilter:非ハラルユーザーが消費を許可されているかどうか
判断逻辑是先通过SpecificCardUserConsumeLimitFilter判断当前用户是否可以在此档口消费,如果允许继续由DayConsumeTimesConsumeLimitFilter判断当天消费次数是否已用完,如果未用完继续由MuslimConsumeLimitFilter判断当前用户是否满足清真餐厅的就餐条件,前面三条判断,只要有一个不满足就提前返回。
部分代码如下:
public boolean canConsume(String uid,String shopId,String supplierId){
//获取用户信息,用户信息包含类型(student:学生,teacher:老师,unknown:未知用户)、名族(han:汉族,mg:蒙古族)
UserInfo userInfo = getUserInfo(uid);
//获取消费限制信息,限制信息包含是否允许非清真消费、每种类型的用户是否允许消费以及允许消费的次数
ConsumeConfigInfo consumeConfigInfo = getConsumeConfigInfo(shopId,supplierId)
// 构造消费限制过滤器链条
ConsumeLimitFilterChain filterChain = new ConsumeLimitFilterChain();
filterChain.addFilter(new SpecificCardUserConsumeLimitFilter());
filterChain.addFilter(new DayConsumeTimesConsumeLimitFilter());
filterChain.addFilter(new MuslimConsumeLimitFilter());
boolean checkResult = filterChain.doFilter(filterChain, schoolMemberInfo, consumeConfigInfo);
//filterChain.doFilter方法
public boolean doFilter(ConsumeLimitFilterChain filterChain,UserInfo userInfo,
ConsumeConfigInfo consumeConfigInfo ){
//迭代调用过滤器
if(index<filters.size()){
return filters.get(index++).doFilter(filterChain, userInfo, consumeConfigInfo);
}
}
//SpecificCardUserConsumeLimitFilter.doFilter方法
public boolean doFilter(ConsumeLimitFilterChain filterChain,UserInfo userInfo,
ConsumeConfigInfo consumeConfigInfo ){
//获取某一类型的消费限制,比如student允许消费,unknown不允许消费
CardConsumeConfig cardConsumeConfig = findSuitCardConfig(userInfo, consumeConfigInfo);
// 判断当前卡用户是否允许消费
if (consumeCardConfig != null) {
if ((!CAN_PAY.equals(cardConsumeConfig .getEnabledPay()))) {
return false;
}
}
//其余情况,继续往后传递
return filterChain.doFilter(filterChain, memberInfo, consumeConfig);
}
//DayConsumeTimesConsumeLimitFilter.doFilter方法
public boolean doFilter(ConsumeLimitFilterChain filterChain,UserInfo userInfo,
ConsumeConfigInfo consumeConfigInfo ){
//获取某一类型的消费限制,比如student可以消费2次
CardConsumeConfig cardConsumeConfig = findSuitCardConfig(userInfo, consumeConfigInfo);
//获取当前用户今天的消费次数
int consumeCnt = getConsumeCnt(userInfo)
if(consumeCnt >= cardConsumeConfig.getDayConsumeTimesLimit()){
return false;
}
//其余情况,继续往后传递
return filterChain.doFilter(filterChain, memberInfo, consumeConfig);
}
总结
将每种限制条件的判断逻辑封装到了具体的Filter中,如果某种限制条件的逻辑有修改不会影响其他条件,如果需要新加限制条件只需要重新构造一个Filter织入到FilterChain上即可。
策略设计模式
定义
定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换
适用场景
主要是为了消除大量的if else代码,将每种判断背后的算法逻辑提取到具体的策略对象中,当算法逻辑修改时对使用者无感知,只需要修改策略对象内部逻辑即可。这类策略对象一般都实现了某个共同的接口,可以达到互换的目的。
实践经验
笔者之前有个需求是用户扫码支付以后向档口的收银设备推送一条支付消息,收银设备收到消息以后会进行语音播报,逻辑很简单,就是调用推送平台推送一条消息给设备即可,但是由于历史原因,某些设备对接的推送平台是不一样的,A类设备优先使用信鸽推送,如果失败了需要降级到长轮询机制,B类设备直接使用自研的推送平台即可,还有个现状是A类和B类的消息格式是不一样的(不同的团队开发,后期被整合到一起),鉴于此,我抽象出PushStrategy接口,其具体的实现有IotPushStrategy和XingePushStrategy,分别对应自研推送平台的推送策略和信鸽平台的推送策略,使用者时针对不同的设备类型使用不同的推送策略即可。部分代码如下:
/**
* 推送策略
* /
public interface PushStrategy {
/**
@param deviceVO设备对象,包扣设备sn,信鸽pushid
@param content,推送内容,一般为json
*/
public CallResult push(AppDeviceVO deviceVO, Object content);
}
IotPushStrategy implements PushStrategy{
/**
@param deviceVO设备对象,包扣设备sn,信鸽pushid
@param content,推送内容,一般为json
*/
public CallResult push(AppDeviceVO deviceVO, Object content){
//创建自研推送平台需要的推送报文
Message message = createPushMsg(deviceVO,content);
//调用推送平台推送接口
IotMessageService.pushMsg(message);
}
}
XingePushStrategy implements PushStrategy{
/**
@param deviceVO设备对象,包扣设备sn,信鸽pushid
@param content,推送内容,一般为json
*/
public CallResult push(AppDeviceVO deviceVO, Object content){
//创建信鸽平台需要的推送报文
JSONObject jsonObject = createPushMsg(content);
//调用推送平台推送接口
if(!XinggePush.pushMsg(message)){
//降级到长轮询
...
}
}
}
/**
消息推送Service
*/
MessagePushService{
pushMsg(AppDeviceVO deviceVO, Object content){
if(A设备){
XingePushStrategy.push(deviceVO,content);
} else if(B设备){
IotPushStrategy.push(deviceVO,content);
}
}
}
总结
各チャネルのプッシュロジックは特定の戦略にカプセル化されます。特定の戦略を変更しても、他の戦略には影響しません。共通のインターフェイスが実装されているため、戦略を相互に置き換えることができます。これは、java ThreadPoolExecutorのタスク拒否戦略など、ユーザーフレンドリーです。 、スレッドプールが飽和状態になると、拒否戦略が実行され、特定の拒否ロジックがRejectedExecutionHandlerのrejectedExecutionにカプセル化されます。
テンプレートデザインパターン
定義
テンプレートの価値はスケルトンの定義にあります。スケルトン内の問題処理のプロセスが定義されています。一般的な処理ロジックは通常、親クラスによって実装され、パーソナライズされた処理ロジックはサブクラスによって実装されます。たとえば、揚げポテトシュレッドと揚げマポ豆腐の一般的な論理は、1。野菜を切る、2。油を入れる、3。かき混ぜる、4。調理する、1、2、4ですが、3番目のステップが異なります。ポテトシュレッドの炒め物はシャベルで炒めなければなりませんが、マポ豆腐の炒め物はスプーンで少しずつ動かす必要があります。そうしないと豆腐が腐ってしまいます。
使用するシーン
さまざまなシナリオの処理フローでは、ロジックの一部はユニバーサルであり、ユニバーサル実装として親クラスに配置できます。ロジックの一部はパーソナライズされており、サブクラスで実装する必要があります。
経験
前の音声ブロードキャストの例に従って、後で2つの新しい要件を追加しました。1。メッセージプッシュでトレースを追加する必要がある2.一部のチャネルプッシュが失敗して再試行する必要があるため、現在のプロセスは次のようになります。1。トレースが開始されます2.チャネルプッシュを開始します。3。再試行を許可するかどうか、再試行ロジックの実行が許可されている場合4.トレースは1と4がユニバーサル、2と3がパーソナライズされている場合に終了します。これを考慮して、特定のプッシュ戦略の前に親クラスを追加しました。一般的なロジックを親クラスに配置する戦略、変更されたコードは次のとおりです。
abstract class AbstractPushStrategy implements PushStrategy{
@Override
public CallResult push(AppDeviceVO deviceVO, Object content) {
//1.构造span
Span span = buildSpan();
//2.具体通道推送逻辑由子类实现
CallResult callResult = doPush(deviceVO, content);
//3.是否允许重试逻辑由子类实现,如果允许执行重试逻辑
if(!callResult.isSuccess() && canRetry()){
doPush(deviceVO, content);
}
//4.trace结束
span.finish()
}
//具体推送逻辑由子类实现
protected abstract CallResult doPush(AppDeviceVO deviceDO, Object content) ;
//是否允许重试由子类实现,有些通道之前没有做消息排重,所有不能重试
protected abstract boolean canRetry(CallResult callResult);
}
XingePushStrategy extends AbstractPushStrategy{
@Override
protected CallResult doPush(AppDeviceVO deviceDO, Object content) {
//执行推送逻辑
}
@Override
protected boolean canRetry(CallResult callResult){
return false
}
}
総括する
プロセスはテンプレートを介して定義され、共通ロジックは親クラスに実装されるため、反復コードが削減されます。パーソナライズされたロジックはサブクラス自体によって実装され、サブクラス間のコードの変更が相互に干渉したり、プロセスを破壊したりすることはありません。
オブザーバーの設計パターン
モード定義
名前が示すように、このモードにはObserverとObservableの2つの役割が必要です。Observableのステータスが変更されると、Observerに通知されます。Observerは通常、Observableに必要なjava.util.Observerなどの共通インターフェイスを実装します。オブザーバーに通知するときは、オブザーバーの更新メソッドを1つずつ呼び出すだけです。オブザーバー処理の成功または失敗は、Observableプロセスに影響を与えないはずです。
使用するシーン
オブジェクト(Observable)のステータス変更は、他のオブジェクトに通知する必要があります。Observerの存在はObservableの処理結果に影響しません。Observerの追加と削除は、KafkaのメッセージサブスクリプションなどのObservableを認識せず、プロデューサーはトピックにメッセージを送信します。1か10かは関係ありません。消費者はこのトピックに同意し、生産者は注意を払う必要はありません。
経験
責任連鎖設計モデルでは、3つのフィルターで消費制限検査の問題を解決しました。フィルターの1つを使用して消費数を確認します。ここではユーザーの消費時間を読み取るだけですが、消費時間の累積はどのように完了しますか?どうですか?実際には、オブザーバーモードが累積に使用されます。具体的には、トランザクションシステムは、支払い成功コールバックを受信すると、音声ブロードキャストの消費数とサブスクリプションの累積を担当するSpringイベントメカニズムを介して「支払い成功イベント」を公開します。その人は「支払い成功イベント」を受け取り、次に独自のビジネスロジックを実行し、次のことを説明する簡単な図を描きます。
コード構造はおおまかに次のとおりです。
/**
支付回调处理者
*/
PayCallBackController implements ApplicationContextAware {
private ApplicationContext applicationContext;
//如果想获取applicationContext需要实现ApplicationContextAware接口,Spring容器会回调setApplicationContext方法将applicationContext注入进来
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@RequestMapping(value = "/pay/callback.do")
public View callback(HttpServletRequest request){
if(paySuccess(request){
//构造支付成功事件
PaySuccessEvent event = buildPaySuccessEvent(...);
//通过applicationContext发布事件,从而达到通知观察者的目的
this.applicationContext.publishEvent(event);
}
}
}
/**
* 语音播报处理者
*
*/
public class VoiceBroadcastHandler implements ApplicationListener<PaySuccessEvent>{
@Override
public void onApplicationEvent(PaySuccessEvent event) {
//语音播报逻辑
}
}
//其他处理者的逻辑类似
総括する
オブザーバーモードはオブザーバーとオブザーバーを分離し、オブザーバーの有無はオブザーバーの既存のロジックに影響を与えません。
デコレータのデザインパターン
定義
デコレータは、元のクラスをラップし、ユーザーに対して透過的に関数を拡張するために使用されます。たとえば、javaのBufferedInputStreamは、ラップするInputStreamを拡張してバッファリング関数を提供できます。
使用するシーン
元のクラスの機能を強化したいが、あまり多くのサブクラスを追加したくない場合は、デコレータモードを使用して同じ効果を実現できます。
経験
著者は以前、会社全体にトレースシステムへのアクセスを宣伝していたので、トレースの自動ウィービングとコンテキストの自動送信を解決するためのツールもいくつか提供しました。興味がある場合は、スレッド間をサポートするために、予備調査を使用して他のブログイェーガーを読むことができます。コンテキスト転送のために、装飾クラスTraceRunnableWrapperを追加して、親スレッドのコンテキストを子スレッドに透過的に転送します。これは、ユーザーに対して完全に透過的です。コードは次のとおりです。
/**
可以自动携带trace上下文的Runnable装饰器
*/
public class TraceRunnableWrapper implements Runnable{
//被包装的目标对象
private Runnable task;
private Span parentSpan = null;
public TraceRunnableWrapper(Runnable task) {
//1.获取当前线程的上下文(因为new的时候还没有发生线程切换,所以需要在这里将上下文获取)
//对这块代码感兴趣的可以查看opentracing API
io.opentracing.Scope currentScope = GlobalTracer.get().scopeManager().active();
//2.保存父上下文
parentSpan = currentScope.span();
this.task = task;
}
@Override
public void run() {
//run的时候将父线程的上下文绑定到当前线程
io.opentracing.Scope scope = GlobalTracer.get().scopeManager().activate(parentSpan,false);
task.run();
}
}
//使用者
new Thread(new Runnable(){run(...)}).start()替换为new TraceRunnableWrapper(new Runnable(){run(...)}).start()
総括する
デコレータモードを使用して機能を強化します。ユーザーは単純な組み合わせを作成するだけで、元の機能を引き続き使用できます。
外観デザインモード
定義
外観とは、外の世界への統一された入り口を提供することです。1つはシステムの詳細を非表示にすることであり、もう1つはユーザーの複雑さを軽減することです。たとえば、SpringMvcのDispaterServletでは、すべてのコントローラーがDispaterServletを介して公開されます。
使用するシーン
ユーザーの複雑さを軽減し、クライアントアクセスコストを簡素化します。
経験
著者の会社は、機器の管理と制御、統一された支払い、明細書のダウンロード機能など、サードパーティのISVにいくつかのオープン機能を提供しています。これらは異なるチームに属しているため、提供される外部インターフェイスはさまざまな形式です。それ以上あれば、ISVも受け入れることができますが、インターフェースが増えると、ISVはアクセスコストが高いと不満を漏らし始めました。この問題を解決するために、オープンインターフェースの前にフロントエンドコントローラーGatewayControllerを追加しました。これは、実際には後のオープンプラットフォームのプロトタイプです。 、GatewayControllerは、インターフェイスgateway.doを外部に均一に公開します。外部インターフェイスの要求パラメータと応答パラメータは、収束のためにGatewayControllerで統合されます。GatewayControllerは、バックエンドサービスにルーティングするときにも統合インターフェイスを使用します。変換前後の比較は次のとおりです。
おそらくコードは次のとおりです。
使用者:
HttpClient.doPost("/gateway.do","{'method':'trade.create','sign':'wxxaaa','timestamp':'15311111111'},'bizContent':'业务参数'")
GatewayController:
@RequestMapping("/gateway.do")
JSON gateway(HttpServletRequest req){
//1.组装开放请求
OpenRequest openRequest = buildOpenRequest(req);
OpenResponse openResponse = null;
//2.请求路由
if("trade.create".equals(openRequest.getMethod()){
//proxy to trade service by dubbo
openResponse = TradeFacade.execute(genericParam);
} else if("iot.message.push".equals(openRequest.getMethod()){
//proxy to iot service by httpclient
openResponse = HttpClient.doPost('http://iot.service/generic/execute'genericParam);
}
if(openResponse.isSuccess()){
return {"code":"10000","bizContent":openResponse.getResult()};
}else{
return {"code":"20000","bizCode":openResponse.getCode()};
}
}
総括する
アピアランスモードは、システム内の一部の詳細を保護し、ユーザーのアクセスコストを削減するために使用されます。GatewayControllerを例にとると、ISV認証、インターフェイス検証、およびその他の反復タスクが統合されます。ISVは異なるインターフェイスを接続するだけで済みます。一連のインターフェイスプロトコルインターフェイスについては、GatewayController層によって収束が行われます。