mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-25 20:56:42 +08:00
1623 lines
32 KiB
HTML
1623 lines
32 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
||
<head>
|
||
|
||
<head>
|
||
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
|
||
<link rel="icon" href="/static/favicon.png">
|
||
|
||
<title>30 装饰器模式:如何优化电商系统中复杂的商品价格策略?.md.html</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
|
||
|
||
|
||
<body>
|
||
|
||
|
||
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/00 开篇词你为什么需要学习并发编程?.md.html">00 开篇词你为什么需要学习并发编程?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/01 如何制定性能调优标准?.md.html">01 如何制定性能调优标准?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/02 如何制定性能调优策略?.md.html">02 如何制定性能调优策略?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/03 字符串性能优化不容小觑,百M内存轻松存储几十G数据.md.html">03 字符串性能优化不容小觑,百M内存轻松存储几十G数据.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/04 慎重使用正则表达式.md.html">04 慎重使用正则表达式.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/05 ArrayList还是LinkedList?使用不当性能差千倍.md.html">05 ArrayList还是LinkedList?使用不当性能差千倍.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/06 Stream如何提高遍历集合效率?.md.html">06 Stream如何提高遍历集合效率?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/07 深入浅出HashMap的设计与优化.md.html">07 深入浅出HashMap的设计与优化.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/08 网络通信优化之IO模型:如何解决高并发下IO瓶颈?.md.html">08 网络通信优化之IO模型:如何解决高并发下IO瓶颈?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/09 网络通信优化之序列化:避免使用Java序列化.md.html">09 网络通信优化之序列化:避免使用Java序列化.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/10 网络通信优化之通信协议:如何优化RPC网络通信?.md.html">10 网络通信优化之通信协议:如何优化RPC网络通信?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/11 答疑课堂:深入了解NIO的优化实现原理.md.html">11 答疑课堂:深入了解NIO的优化实现原理.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/12 多线程之锁优化(上):深入了解Synchronized同步锁的优化方法.md.html">12 多线程之锁优化(上):深入了解Synchronized同步锁的优化方法.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/13 多线程之锁优化(中):深入了解Lock同步锁的优化方法.md.html">13 多线程之锁优化(中):深入了解Lock同步锁的优化方法.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/14 多线程之锁优化(下):使用乐观锁优化并行操作.md.html">14 多线程之锁优化(下):使用乐观锁优化并行操作.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/15 多线程调优(上):哪些操作导致了上下文切换?.md.html">15 多线程调优(上):哪些操作导致了上下文切换?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/16 多线程调优(下):如何优化多线程上下文切换?.md.html">16 多线程调优(下):如何优化多线程上下文切换?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/17 并发容器的使用:识别不同场景下最优容器.md.html">17 并发容器的使用:识别不同场景下最优容器.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/18 如何设置线程池大小?.md.html">18 如何设置线程池大小?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/19 如何用协程来优化多线程业务?.md.html">19 如何用协程来优化多线程业务?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/20 磨刀不误砍柴工:欲知JVM调优先了解JVM内存模型.md.html">20 磨刀不误砍柴工:欲知JVM调优先了解JVM内存模型.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/21 深入JVM即时编译器JIT,优化Java编译.md.html">21 深入JVM即时编译器JIT,优化Java编译.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/22 如何优化垃圾回收机制?.md.html">22 如何优化垃圾回收机制?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/23 如何优化JVM内存分配?.md.html">23 如何优化JVM内存分配?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/24 内存持续上升,我该如何排查问题?.md.html">24 内存持续上升,我该如何排查问题?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/25 答疑课堂:模块四热点问题解答.md.html">25 答疑课堂:模块四热点问题解答.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/26 单例模式:如何创建单一对象优化系统性能?.md.html">26 单例模式:如何创建单一对象优化系统性能?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/27 原型模式与享元模式:提升系统性能的利器.md.html">27 原型模式与享元模式:提升系统性能的利器.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/28 如何使用设计模式优化并发编程?.md.html">28 如何使用设计模式优化并发编程?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/29 生产者消费者模式:电商库存设计优化.md.html">29 生产者消费者模式:电商库存设计优化.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
<a class="current-tab" href="/专栏/Java并发编程实战/30 装饰器模式:如何优化电商系统中复杂的商品价格策略?.md.html">30 装饰器模式:如何优化电商系统中复杂的商品价格策略?.md.html</a>
|
||
|
||
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/31 答疑课堂:模块五思考题集锦.md.html">31 答疑课堂:模块五思考题集锦.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/32 MySQL调优之SQL语句:如何写出高性能SQL语句?.md.html">32 MySQL调优之SQL语句:如何写出高性能SQL语句?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/33 MySQL调优之事务:高并发场景下的数据库事务调优.md.html">33 MySQL调优之事务:高并发场景下的数据库事务调优.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/34 MySQL调优之索引:索引的失效与优化.md.html">34 MySQL调优之索引:索引的失效与优化.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/35 记一次线上SQL死锁事故:如何避免死锁?.md.html">35 记一次线上SQL死锁事故:如何避免死锁?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/36 什么时候需要分表分库?.md.html">36 什么时候需要分表分库?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/37 电商系统表设计优化案例分析.md.html">37 电商系统表设计优化案例分析.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/38 数据库参数设置优化,失之毫厘差之千里.md.html">38 数据库参数设置优化,失之毫厘差之千里.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/39 答疑课堂:MySQL中InnoDB的知识点串讲.md.html">39 答疑课堂:MySQL中InnoDB的知识点串讲.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/41 如何设计更优的分布式锁?.md.html">41 如何设计更优的分布式锁?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/42 电商系统的分布式事务调优.md.html">42 电商系统的分布式事务调优.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/43 如何使用缓存优化系统性能?.md.html">43 如何使用缓存优化系统性能?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/44 记一次双十一抢购性能瓶颈调优.md.html">44 记一次双十一抢购性能瓶颈调优.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/加餐 什么是数据的强、弱一致性?.md.html">加餐 什么是数据的强、弱一致性?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/加餐 推荐几款常用的性能测试工具.md.html">加餐 推荐几款常用的性能测试工具.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/答疑课堂:模块三热点问题解答.md.html">答疑课堂:模块三热点问题解答.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/结束语 栉风沐雨,砥砺前行!.md.html">结束语 栉风沐雨,砥砺前行!.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
|
||
<div class="sidebar-toggle-inner"></div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<script>
|
||
|
||
function add_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.add('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function remove_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function sidebar_toggle() {
|
||
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let content = document.querySelector('.off-canvas-content')
|
||
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
|
||
sidebar_toggle.classList.remove('extend')
|
||
|
||
sidebar.classList.remove('hide')
|
||
|
||
content.classList.remove('extend')
|
||
|
||
} else { // hide
|
||
|
||
sidebar_toggle.classList.add('extend')
|
||
|
||
sidebar.classList.add('hide')
|
||
|
||
content.classList.add('extend')
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
function open_sidebar() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.add('show')
|
||
|
||
overlay.classList.add('show')
|
||
|
||
}
|
||
|
||
function hide_canvas() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.remove('show')
|
||
|
||
overlay.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
<div class="off-canvas-content">
|
||
|
||
<div class="columns">
|
||
|
||
<div class="column col-12 col-lg-12">
|
||
|
||
<div class="book-navbar">
|
||
|
||
<!-- For Responsive Layout -->
|
||
|
||
<header class="navbar">
|
||
|
||
<section class="navbar-section">
|
||
|
||
<a onclick="open_sidebar()">
|
||
|
||
<i class="icon icon-menu"></i>
|
||
|
||
</a>
|
||
|
||
</section>
|
||
|
||
</header>
|
||
|
||
</div>
|
||
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
|
||
overflow-x: auto;
|
||
|
||
overflow-y: hidden;">
|
||
|
||
<div class="book-post">
|
||
|
||
<p id="tip" align="center"></p>
|
||
|
||
<div><h1>30 装饰器模式:如何优化电商系统中复杂的商品价格策略?</h1>
|
||
|
||
<p>你好,我是刘超。</p>
|
||
|
||
<p>开始今天的学习之前,我想先请你思考一个问题。假设现在有这样一个需求,让你设计一个装修功能,用户可以动态选择不同的装修功能来装饰自己的房子。例如,水电装修、天花板以及粉刷墙等属于基本功能,而设计窗帘装饰窗户、设计吊顶装饰房顶等未必是所有用户都需要的,这些功能则需要实现动态添加。还有就是一旦有新的装修功能,我们也可以实现动态添加。如果要你来负责,你会怎么设计呢?</p>
|
||
|
||
<p>此时你可能会想了,通常给一个对象添加功能,要么直接修改代码,在对象中添加相应的功能,要么派生对应的子类来扩展。然而,前者每次都需要修改对象的代码,这显然不是理想的面向对象设计,即便后者是通过派生对应的子类来扩展,也很难满足复杂的随意组合功能需求。</p>
|
||
|
||
<p>面对这种情况,使用装饰器模式应该再合适不过了。它的优势我想你多少知道一点,我在这里总结一下。</p>
|
||
|
||
<p>装饰器模式能够实现为对象动态添加装修功能,它是从一个对象的外部来给对象添加功能,所以有非常灵活的扩展性,我们可以在对原来的代码毫无修改的前提下,为对象添加新功能。除此之外,装饰器模式还能够实现对象的动态组合,借此我们可以很灵活地给动态组合的对象,匹配所需要的功能。</p>
|
||
|
||
<p>下面我们就通过实践,具体看看该模式的优势。</p>
|
||
|
||
<h2>什么是装饰器模式?</h2>
|
||
|
||
<p>在这之前,我先简单介绍下什么是装饰器模式。装饰器模式包括了以下几个角色:接口、具体对象、装饰类、具体装饰类。</p>
|
||
|
||
<p>接口定义了具体对象的一些实现方法;具体对象定义了一些初始化操作,比如开头设计装修功能的案例中,水电装修、天花板以及粉刷墙等都是初始化操作;装饰类则是一个抽象类,主要用来初始化具体对象的一个类;其它的具体装饰类都继承了该抽象类。</p>
|
||
|
||
<p>下面我们就通过装饰器模式来实现下装修功能,代码如下:</p>
|
||
|
||
<pre><code>/**
|
||
|
||
* 定义一个基本装修接口
|
||
|
||
* @author admin
|
||
|
||
*
|
||
|
||
*/
|
||
|
||
public interface IDecorator {
|
||
|
||
|
||
|
||
/**
|
||
|
||
* 装修方法
|
||
|
||
*/
|
||
|
||
void decorate();
|
||
|
||
|
||
|
||
}
|
||
|
||
/**
|
||
|
||
* 装修基本类
|
||
|
||
* @author admin
|
||
|
||
*
|
||
|
||
*/
|
||
|
||
public class Decorator implements IDecorator{
|
||
|
||
|
||
|
||
/**
|
||
|
||
* 基本实现方法
|
||
|
||
*/
|
||
|
||
public void decorate() {
|
||
|
||
System.out.println(" 水电装修、天花板以及粉刷墙。。。");
|
||
|
||
}
|
||
|
||
|
||
|
||
}
|
||
|
||
/**
|
||
|
||
* 基本装饰类
|
||
|
||
* @author admin
|
||
|
||
*
|
||
|
||
*/
|
||
|
||
public abstract class BaseDecorator implements IDecorator{
|
||
|
||
|
||
|
||
private IDecorator decorator;
|
||
|
||
|
||
|
||
public BaseDecorator(IDecorator decorator) {
|
||
|
||
this.decorator = decorator;
|
||
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
|
||
* 调用装饰方法
|
||
|
||
*/
|
||
|
||
public void decorate() {
|
||
|
||
if(decorator != null) {
|
||
|
||
decorator.decorate();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
/**
|
||
|
||
* 窗帘装饰类
|
||
|
||
* @author admin
|
||
|
||
*
|
||
|
||
*/
|
||
|
||
public class CurtainDecorator extends BaseDecorator{
|
||
|
||
|
||
|
||
public CurtainDecorator(IDecorator decorator) {
|
||
|
||
super(decorator);
|
||
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
|
||
* 窗帘具体装饰方法
|
||
|
||
*/
|
||
|
||
@Override
|
||
|
||
public void decorate() {
|
||
|
||
System.out.println(" 窗帘装饰。。。");
|
||
|
||
super.decorate();
|
||
|
||
}
|
||
|
||
|
||
|
||
}
|
||
|
||
public static void main( String[] args )
|
||
|
||
{
|
||
|
||
IDecorator decorator = new Decorator();
|
||
|
||
IDecorator curtainDecorator = new CurtainDecorator(decorator);
|
||
|
||
curtainDecorator.decorate();
|
||
|
||
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>运行结果:</p>
|
||
|
||
<pre><code>窗帘装饰。。。
|
||
|
||
水电装修、天花板以及粉刷墙。。。
|
||
|
||
</code></pre>
|
||
|
||
<p>通过这个案例,我们可以了解到:如果我们想要在基础类上添加新的装修功能,只需要基于抽象类 BaseDecorator 去实现继承类,通过构造函数调用父类,以及重写装修方法实现装修窗帘的功能即可。在 main 函数中,我们通过实例化装饰类,调用装修方法,即可在基础装修的前提下,获得窗帘装修功能。</p>
|
||
|
||
<p>基于装饰器模式实现的装修功能的代码结构简洁易读,业务逻辑也非常清晰,并且如果我们需要扩展新的装修功能,只需要新增一个继承了抽象装饰类的子类即可。</p>
|
||
|
||
<p>在这个案例中,我们仅实现了业务扩展功能,接下来,我将通过装饰器模式优化电商系统中的商品价格策略,实现不同促销活动的灵活组合。</p>
|
||
|
||
<h2>优化电商系统中的商品价格策略</h2>
|
||
|
||
<p>相信你一定不陌生,购买商品时经常会用到的限时折扣、红包、抵扣券以及特殊抵扣金等,种类很多,如果换到开发视角,实现起来就更复杂了。</p>
|
||
|
||
<p>例如,每逢双十一,为了加大商城的优惠力度,开发往往要设计红包 + 限时折扣或红包 + 抵扣券等组合来实现多重优惠。而在平时,由于某些特殊原因,商家还会赠送特殊抵扣券给购买用户,而特殊抵扣券 + 各种优惠又是另一种组合方式。</p>
|
||
|
||
<p>要实现以上这类组合优惠的功能,最快、最普遍的实现方式就是通过大量 if-else 的方式来实现。但这种方式包含了大量的逻辑判断,致使其他开发人员很难读懂业务, 并且一旦有新的优惠策略或者价格组合策略出现,就需要修改代码逻辑。</p>
|
||
|
||
<p>这时,刚刚介绍的装饰器模式就很适合用在这里,其相互独立、自由组合以及方便动态扩展功能的特性,可以很好地解决 if-else 方式的弊端。下面我们就用装饰器模式动手实现一套商品价格策略的优化方案。</p>
|
||
|
||
<p>首先,我们先建立订单和商品的属性类,在本次案例中,为了保证简洁性,我只建立了几个关键字段。以下几个重要属性关系为,主订单包含若干详细订单,详细订单中记录了商品信息,商品信息中包含了促销类型信息,一个商品可以包含多个促销类型(本案例只讨论单个促销和组合促销):</p>
|
||
|
||
<pre><code>/**
|
||
|
||
* 主订单
|
||
|
||
* @author admin
|
||
|
||
*
|
||
|
||
*/
|
||
|
||
public class Order {
|
||
|
||
|
||
|
||
private int id; // 订单 ID
|
||
|
||
private String orderNo; // 订单号
|
||
|
||
private BigDecimal totalPayMoney; // 总支付金额
|
||
|
||
private List<OrderDetail> list; // 详细订单列表
|
||
|
||
}
|
||
|
||
/**
|
||
|
||
* 详细订单
|
||
|
||
* @author admin
|
||
|
||
*
|
||
|
||
*/
|
||
|
||
public class OrderDetail {
|
||
|
||
private int id; // 详细订单 ID
|
||
|
||
private int orderId;// 主订单 ID
|
||
|
||
private Merchandise merchandise; // 商品详情
|
||
|
||
private BigDecimal payMoney; // 支付单价
|
||
|
||
}
|
||
|
||
/**
|
||
|
||
* 商品
|
||
|
||
* @author admin
|
||
|
||
*
|
||
|
||
*/
|
||
|
||
public class Merchandise {
|
||
|
||
|
||
|
||
private String sku;// 商品 SKU
|
||
|
||
private String name; // 商品名称
|
||
|
||
private BigDecimal price; // 商品单价
|
||
|
||
private Map<PromotionType, SupportPromotions> supportPromotions; // 支持促销类型
|
||
|
||
}
|
||
|
||
/**
|
||
|
||
* 促销类型
|
||
|
||
* @author admin
|
||
|
||
*
|
||
|
||
*/
|
||
|
||
public class SupportPromotions implements Cloneable{
|
||
|
||
|
||
|
||
private int id;// 该商品促销的 ID
|
||
|
||
private PromotionType promotionType;// 促销类型 1\优惠券 2\红包
|
||
|
||
private int priority; // 优先级
|
||
|
||
private UserCoupon userCoupon; // 用户领取该商品的优惠券
|
||
|
||
private UserRedPacket userRedPacket; // 用户领取该商品的红包
|
||
|
||
|
||
|
||
// 重写 clone 方法
|
||
|
||
public SupportPromotions clone(){
|
||
|
||
SupportPromotions supportPromotions = null;
|
||
|
||
try{
|
||
|
||
supportPromotions = (SupportPromotions)super.clone();
|
||
|
||
}catch(CloneNotSupportedException e){
|
||
|
||
e.printStackTrace();
|
||
|
||
}
|
||
|
||
return supportPromotions;
|
||
|
||
}
|
||
|
||
}
|
||
|
||
/**
|
||
|
||
* 优惠券
|
||
|
||
* @author admin
|
||
|
||
*
|
||
|
||
*/
|
||
|
||
public class UserCoupon {
|
||
|
||
|
||
|
||
private int id; // 优惠券 ID
|
||
|
||
private int userId; // 领取优惠券用户 ID
|
||
|
||
private String sku; // 商品 SKU
|
||
|
||
private BigDecimal coupon; // 优惠金额
|
||
|
||
}
|
||
|
||
/**
|
||
|
||
* 红包
|
||
|
||
* @author admin
|
||
|
||
*
|
||
|
||
*/
|
||
|
||
public class UserRedPacket {
|
||
|
||
|
||
|
||
private int id; // 红包 ID
|
||
|
||
private int userId; // 领取用户 ID
|
||
|
||
private String sku; // 商品 SKU
|
||
|
||
private BigDecimal redPacket; // 领取红包金额
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>接下来,我们再建立一个计算支付金额的接口类以及基本类:</p>
|
||
|
||
<pre><code>/**
|
||
|
||
* 计算支付金额接口类
|
||
|
||
* @author admin
|
||
|
||
*
|
||
|
||
*/
|
||
|
||
public interface IBaseCount {
|
||
|
||
|
||
|
||
BigDecimal countPayMoney(OrderDetail orderDetail);
|
||
|
||
|
||
|
||
}
|
||
|
||
/**
|
||
|
||
* 支付基本类
|
||
|
||
* @author admin
|
||
|
||
*
|
||
|
||
*/
|
||
|
||
public class BaseCount implements IBaseCount{
|
||
|
||
|
||
|
||
public BigDecimal countPayMoney(OrderDetail orderDetail) {
|
||
|
||
orderDetail.setPayMoney(orderDetail.getMerchandise().getPrice());
|
||
|
||
System.out.println(" 商品原单价金额为:" + orderDetail.getPayMoney());
|
||
|
||
|
||
|
||
return orderDetail.getPayMoney();
|
||
|
||
}
|
||
|
||
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>然后,我们再建立一个计算支付金额的抽象类,由抽象类调用基本类:</p>
|
||
|
||
<pre><code>/**
|
||
|
||
* 计算支付金额的抽象类
|
||
|
||
* @author admin
|
||
|
||
*
|
||
|
||
*/
|
||
|
||
public abstract class BaseCountDecorator implements IBaseCount{
|
||
|
||
|
||
|
||
private IBaseCount count;
|
||
|
||
|
||
|
||
public BaseCountDecorator(IBaseCount count) {
|
||
|
||
this.count = count;
|
||
|
||
}
|
||
|
||
|
||
|
||
public BigDecimal countPayMoney(OrderDetail orderDetail) {
|
||
|
||
BigDecimal payTotalMoney = new BigDecimal(0);
|
||
|
||
if(count!=null) {
|
||
|
||
payTotalMoney = count.countPayMoney(orderDetail);
|
||
|
||
}
|
||
|
||
return payTotalMoney;
|
||
|
||
}
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>然后,我们再通过继承抽象类来实现我们所需要的修饰类(优惠券计算类、红包计算类):</p>
|
||
|
||
<pre><code>/**
|
||
|
||
* 计算使用优惠券后的金额
|
||
|
||
* @author admin
|
||
|
||
*
|
||
|
||
*/
|
||
|
||
public class CouponDecorator extends BaseCountDecorator{
|
||
|
||
|
||
|
||
public CouponDecorator(IBaseCount count) {
|
||
|
||
super(count);
|
||
|
||
}
|
||
|
||
|
||
|
||
public BigDecimal countPayMoney(OrderDetail orderDetail) {
|
||
|
||
BigDecimal payTotalMoney = new BigDecimal(0);
|
||
|
||
payTotalMoney = super.countPayMoney(orderDetail);
|
||
|
||
payTotalMoney = countCouponPayMoney(orderDetail);
|
||
|
||
return payTotalMoney;
|
||
|
||
}
|
||
|
||
|
||
|
||
private BigDecimal countCouponPayMoney(OrderDetail orderDetail) {
|
||
|
||
|
||
|
||
BigDecimal coupon = orderDetail.getMerchandise().getSupportPromotions().get(PromotionType.COUPON).getUserCoupon().getCoupon();
|
||
|
||
System.out.println(" 优惠券金额:" + coupon);
|
||
|
||
|
||
|
||
orderDetail.setPayMoney(orderDetail.getPayMoney().subtract(coupon));
|
||
|
||
return orderDetail.getPayMoney();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
/**
|
||
|
||
* 计算使用红包后的金额
|
||
|
||
* @author admin
|
||
|
||
*
|
||
|
||
*/
|
||
|
||
public class RedPacketDecorator extends BaseCountDecorator{
|
||
|
||
|
||
|
||
public RedPacketDecorator(IBaseCount count) {
|
||
|
||
super(count);
|
||
|
||
}
|
||
|
||
|
||
|
||
public BigDecimal countPayMoney(OrderDetail orderDetail) {
|
||
|
||
BigDecimal payTotalMoney = new BigDecimal(0);
|
||
|
||
payTotalMoney = super.countPayMoney(orderDetail);
|
||
|
||
payTotalMoney = countCouponPayMoney(orderDetail);
|
||
|
||
return payTotalMoney;
|
||
|
||
}
|
||
|
||
|
||
|
||
private BigDecimal countCouponPayMoney(OrderDetail orderDetail) {
|
||
|
||
|
||
|
||
BigDecimal redPacket = orderDetail.getMerchandise().getSupportPromotions().get(PromotionType.REDPACKED).getUserRedPacket().getRedPacket();
|
||
|
||
System.out.println(" 红包优惠金额:" + redPacket);
|
||
|
||
|
||
|
||
orderDetail.setPayMoney(orderDetail.getPayMoney().subtract(redPacket));
|
||
|
||
return orderDetail.getPayMoney();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>最后,我们通过一个工厂类来组合商品的促销类型:</p>
|
||
|
||
<pre><code>/**
|
||
|
||
* 计算促销后的支付价格
|
||
|
||
* @author admin
|
||
|
||
*
|
||
|
||
*/
|
||
|
||
public class PromotionFactory {
|
||
|
||
|
||
|
||
public static BigDecimal getPayMoney(OrderDetail orderDetail) {
|
||
|
||
|
||
|
||
// 获取给商品设定的促销类型
|
||
|
||
Map<PromotionType, SupportPromotions> supportPromotionslist = orderDetail.getMerchandise().getSupportPromotions();
|
||
|
||
|
||
|
||
// 初始化计算类
|
||
|
||
IBaseCount baseCount = new BaseCount();
|
||
|
||
if(supportPromotionslist!=null && supportPromotionslist.size()>0) {
|
||
|
||
for(PromotionType promotionType: supportPromotionslist.keySet()) {// 遍历设置的促销类型,通过装饰器组合促销类型
|
||
|
||
baseCount = protmotion(supportPromotionslist.get(promotionType), baseCount);
|
||
|
||
}
|
||
|
||
}
|
||
|
||
return baseCount.countPayMoney(orderDetail);
|
||
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
|
||
* 组合促销类型
|
||
|
||
* @param supportPromotions
|
||
|
||
* @param baseCount
|
||
|
||
* @return
|
||
|
||
*/
|
||
|
||
private static IBaseCount protmotion(SupportPromotions supportPromotions, IBaseCount baseCount) {
|
||
|
||
if(supportPromotions.getPromotionType()==PromotionType.COUPON) {
|
||
|
||
baseCount = new CouponDecorator(baseCount);
|
||
|
||
}else if(supportPromotions.getPromotionType()==PromotionType.REDPACKED) {
|
||
|
||
baseCount = new RedPacketDecorator(baseCount);
|
||
|
||
}
|
||
|
||
return baseCount;
|
||
|
||
}
|
||
|
||
|
||
|
||
}
|
||
|
||
public static void main( String[] args ) throws InterruptedException, IOException
|
||
|
||
{
|
||
|
||
Order order = new Order();
|
||
|
||
init(order);
|
||
|
||
|
||
|
||
for(OrderDetail orderDetail: order.getList()) {
|
||
|
||
BigDecimal payMoney = PromotionFactory.getPayMoney(orderDetail);
|
||
|
||
orderDetail.setPayMoney(payMoney);
|
||
|
||
System.out.println(" 最终支付金额:" + orderDetail.getPayMoney());
|
||
|
||
}
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>运行结果:</p>
|
||
|
||
<pre><code>商品原单价金额为:20
|
||
|
||
优惠券金额:3
|
||
|
||
红包优惠金额:10
|
||
|
||
最终支付金额:7
|
||
|
||
</code></pre>
|
||
|
||
<p>以上源码可以通过 <a href="https://github.com/nickliuchao/decorator.git">Github</a> 下载运行。通过以上案例可知:使用装饰器模式设计的价格优惠策略,实现各个促销类型的计算功能都是相互独立的类,并且可以通过工厂类自由组合各种促销类型。</p>
|
||
|
||
<h2>总结</h2>
|
||
|
||
<p>这讲介绍的装饰器模式主要用来优化业务的复杂度,它不仅简化了我们的业务代码,还优化了业务代码的结构设计,使得整个业务逻辑清晰、易读易懂。</p>
|
||
|
||
<p>通常,装饰器模式用于扩展一个类的功能,且支持动态添加和删除类的功能。在装饰器模式中,装饰类和被装饰类都只关心自身的业务,不相互干扰,真正实现了解耦。</p>
|
||
|
||
<h2>思考题</h2>
|
||
|
||
<p>责任链模式、策略模式与装饰器模式有很多相似之处。平时,这些设计模式除了在业务中被用到以外,在架构设计中也经常被用到,你是否在源码中见过这几种设计模式的使用场景呢?欢迎你与大家分享。</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/Java并发编程实战/29 生产者消费者模式:电商库存设计优化.md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/Java并发编程实战/31 答疑课堂:模块五思考题集锦.md.html">下一页</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
|
||
</div>
|
||
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"7099719f58cb3d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
|
||
</body>
|
||
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
|
||
<script>
|
||
|
||
window.dataLayer = window.dataLayer || [];
|
||
|
||
|
||
|
||
function gtag() {
|
||
|
||
dataLayer.push(arguments);
|
||
|
||
}
|
||
|
||
|
||
|
||
gtag('js', new Date());
|
||
|
||
gtag('config', 'G-NPSEEVD756');
|
||
|
||
var path = window.location.pathname
|
||
|
||
var cookie = getCookie("lastPath");
|
||
|
||
console.log(path)
|
||
|
||
if (path.replace("/", "") === "") {
|
||
|
||
if (cookie.replace("/", "") !== "") {
|
||
|
||
console.log(cookie)
|
||
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
setCookie("lastPath", path)
|
||
|
||
}
|
||
|
||
|
||
|
||
function setCookie(cname, cvalue) {
|
||
|
||
var d = new Date();
|
||
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
|
||
var expires = "expires=" + d.toGMTString();
|
||
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
|
||
}
|
||
|
||
|
||
|
||
function getCookie(cname) {
|
||
|
||
var name = cname + "=";
|
||
|
||
var ca = document.cookie.split(';');
|
||
|
||
for (var i = 0; i < ca.length; i++) {
|
||
|
||
var c = ca[i].trim();
|
||
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
|
||
}
|
||
|
||
return "";
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
</html>
|
||
|