分享:
kotlin学习练习网站-https://github.com/dbacinski/Design-Patterns-In-Kotlin
kotlin资料网站-
设计模式网站-http://www.runoob.com/design-pattern/decorator-pattern.html
引言:
设计模式的本质是让我们更好的运用面向对象有点,应对项目的后期的需求变更和变化
那么在学习和使用kotlin中结合以往的设计模式会怎么样呢?
以下不是抛开实际需求而是实战中运用到设计模式

主页目录:
策略模式
单例模式
工厂模式
代理模式
观察者模式
建造者模式
适配器模式
责任链模式
命令解释器模式
装饰模式
外观模式
状态模式
过滤器模式
访问者模式

策略模式Strategy功能:为不同的字符串处理提供不同的算法import java.text.DecimalFormat
import java.util.regex.Patterninterface ValueTransform {@Throws(NumberFormatException::class)fun transform(str:String?):String?
}inline fun String.vt(t:ValueTransform): String?{return t.transform(this)
}class NumberFromat(val len:Int):ValueTransform{override fun transform(str: String?): String? {return String.format("%0${len}d",Integer.valueOf(str))}
}
class TimerNumberFromat(val len:Int):ValueTransform{override fun transform(str: String?): String? {var sb=StringBuffer();val pattern = Pattern.compile("[\\d]++|[\\D]++")val matcher = pattern.matcher(str)fun isNumber(str: String):Boolean=Pattern.compile("[0-9]*").matcher(str).matches()while (matcher.find()) {var value=matcher.group()when(isNumber(value)){true -> sb.append(value.vt(NumberFromat(2)))else -> sb.append(value)}}return sb.toString()}
}
class GoldFormat(val len:Int):ValueTransform{override fun transform(str: String?): String? {var sb=StringBuffer(".")if(len<=0){return str?.toDouble()?.toInt().toString()}for(i in 0..len-1) {sb.append("0")}return DecimalFormat(sb.toString()).format(str?.toDouble())}}
fun main(args: Array<String>) {println("9->"+("9".vt(NumberFromat(2))))println("9->"+("9".vt(GoldFormat(2))))println("1989-9-1 1:4:15->"+("1989-9-1 1:4:15".vt(TimerNumberFromat(2))));
}
单例模式Singletonobject PrinterDriver {init {println("Initializing with object: $this")}fun print() = println("Printing with object: $this")
}fun main(args: Array<String>) {println("Start")PrinterDriver.print()PrinterDriver.print()
}
工厂模式Factory功能:减少项目变化所造成的影响/**抽象工厂*/
interface Plantclass OrangePlant : Plantclass ApplePlant : Plantabstract class PlantFactory {abstract fun makePlant(): Plantcompanion object {inline fun <reified T : Plant> createFactory(): PlantFactory = when (T::class) {OrangePlant::class -> OrangeFactory()ApplePlant::class  -> AppleFactory()else               -> throw IllegalArgumentException() as Throwable}}
}class AppleFactory : PlantFactory() {override fun makePlant(): Plant = ApplePlant()
}class OrangeFactory : PlantFactory() {override fun makePlant(): Plant = OrangePlant()
}fun main(args: Array<String>) {val plantFactory = PlantFactory.createFactory<OrangePlant>()val plant = plantFactory.makePlant()println("Created plant: $plant")
}
代理模式ProtectionProxy功能:通过代理禁止访问文件的密码interface File {fun read(name: String)
}class NormalFile : File {override fun read(name: String) = println("Reading file: $name")
}//Proxy:
class SecuredFile : File {val normalFile = NormalFile()var password: String = ""override fun read(name: String) {if (password == "secret") {println("Password is correct: $password")normalFile.read(name)} else {println("Incorrect password. Access denied!")}}
}fun main(args: Array<String>) {val securedFile = SecuredFile()securedFile.read("readme.md")securedFile.password = "secret"securedFile.read("readme.md")
}
观察者模式Observer功能:文本框内容变化时通知其他监听器发生变化import kotlin.properties.Delegatesinterface TextChangedListener {fun onTextChanged(newText: String)
}class PrintingTextChangedListener : TextChangedListener {override fun onTextChanged(newText: String) = println("Text is changed to: $newText")
}class TextView {var listener: TextChangedListener? = nullvar text: String by Delegates.observable("") { prop, old, new ->listener?.onTextChanged(new)}
}fun main(args: Array<String>) {val textView = TextView()textView.listener = PrintingTextChangedListener()textView.text = "Lorem ipsum"textView.text = "dolor sit amet"
}
建造者模式Build功能:创建一个可自定义各种样式的对话框import java.io.File// Let's assume that Dialog class is provided by external library.
// We have only access to Dialog public interface which cannot be changed.
/**建造*/
class Dialog() {fun showTitle() = println("showing title")fun setTitle(text: String) = println("setting title text $text")fun setTitleColor(color: String) = println("setting title color $color")fun showMessage() = println("showing message")fun setMessage(text: String) = println("setting message $text")fun setMessageColor(color: String) = println("setting message color $color")fun showImage(bitmapBytes: ByteArray) = println("showing image with size ${bitmapBytes.size}")fun show() = println("showing dialog $this")
}//Builder:
class DialogBuilder() {constructor(init: DialogBuilder.() -> Unit) : this() {init()}private var titleHolder: TextView? = nullprivate var messageHolder: TextView? = nullprivate var imageHolder: File? = nullfun title(init: TextView.() -> Unit) {titleHolder = TextView().apply { init() }}fun message(init: TextView.() -> Unit) {messageHolder = TextView().apply { init() }}fun image(init: () -> File) {imageHolder = init()}fun build(): Dialog {val dialog = Dialog()titleHolder?.apply {dialog.setTitle(text)dialog.setTitleColor(color)dialog.showTitle()}messageHolder?.apply {dialog.setMessage(text)dialog.setMessageColor(color)dialog.showMessage()}imageHolder?.apply {dialog.showImage(readBytes())}return dialog}class TextView {var text: String = ""var color: String = "#00000"}
}//Function that creates dialog builder and builds Dialog
fun dialog(init: DialogBuilder.() -> Unit): Dialog {return DialogBuilder(init).build()
}fun main(args: Array<String>) {val dialog: Dialog = dialog {title {text = "Dialog Title"}message {text = "Dialog Message"color = "#333333"}image {File.createTempFile("image", "jpg")}}dialog.show()
}
适配器模式Adapter/**适配器模式器模式-将开氏温度和摄氏温度换算*/
interface Temperature {var temperature: Double
}class CelsiusTemperature(override var temperature: Double) : Temperatureclass FahrenheitTemperature(var celsiusTemperature: CelsiusTemperature) : Temperature {override var temperature: Doubleget() = convertCelsiusToFahrenheit(celsiusTemperature.temperature)set(temperatureInF) {celsiusTemperature.temperature = convertFahrenheitToCelsius(temperatureInF)}private fun convertFahrenheitToCelsius(f: Double): Double = (f - 32) * 5 / 9private fun convertCelsiusToFahrenheit(c: Double): Double = (c * 9 / 5) + 32
}fun main(args: Array<String>) {val celsiusTemperature = CelsiusTemperature(0.0)val fahrenheitTemperature = FahrenheitTemperature(celsiusTemperature)celsiusTemperature.temperature = 36.6println("${celsiusTemperature.temperature} C -> ${fahrenheitTemperature.temperature} F")fahrenheitTemperature.temperature = 100.0println("${fahrenheitTemperature.temperature} F -> ${celsiusTemperature.temperature} C")
}
责任链模式ChainOfResponsibility功能:根据url来定义(跳转 打开或者自定义)功能 类似击鼓传花
enum class SchemeAction(var host: String, var action: (Map<String, String>) -> Unit) {OrderUI("scheme://order", {println("start order acitvity - parm=${it["id"]}")}),Evaluate("scheme://evaluate", {println("start evaluate acitvity ${it["id"]}")}),web("", {println("start web")});fun isMatching(url: String): SchemeAction? = when (true) {url.indexOf(host) == 0 -> thiselse -> null;}companion object {fun matching(url: String) {SchemeAction.values().forEach {var scheme = it.isMatching(url)scheme?.action?.invoke(paramToMap(url))scheme?.run { return }}}fun paramToMap(url: String): Map<String, String> = HashMap<String, String>().apply {var arr=url.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()if(arr.size==2){arr[1].split("&".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray().forEach {var parm=it.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()if(parm.size==2)this[parm[0]]=parm[1]}}}}}
fun main(args: Array<String>) {SchemeAction.matching("scheme://evaluate?id=5")SchemeAction.matching("http://www.baidu.com")
}
命令解释器模式CommandandInterpreterPattern功能:给产品或者其他设计定义一种开放简单脚本用来编写剧情import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledThreadPoolExecutor
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantLockclass TimeWait(val time: Long) : Runnable {companion object {val poolExecutor: ScheduledThreadPoolExecutor = ScheduledThreadPoolExecutor(10)}var keepOn = false;var lock = ReentrantLock()var condition = lock.newCondition()override fun run() {println("等待:" + time)poolExecutor.schedule({lock.lock()keepOn = truecondition.signalAll();lock.unlock()}, time, TimeUnit.MILLISECONDS)while (!keepOn) {lock.lock()condition.await()lock.unlock()}}
}
interface Interpreter {fun interpreter(command: String, commandArr: List<String>): Runnable?
}class SkillInterpreter : Interpreter {override fun interpreter(command: String, arr: List<String>): Runnable? {if (arr[0].equals("skill")) {return when (arr[2]) {"eat" -> Runnable { println("${arr[1]} skill ${arr[2]} (O ^ ~ ^ O)") }"ultimate" -> Runnable { println("${arr[1]} skill ${arr[2]}(╯‵□′)╯︵┴─┴") }else -> Runnable { }}}return null}
}class MoveInterpreter : Interpreter {override fun interpreter(command: String, arr: List<String>): Runnable? {if (arr[0].equals("move")) {return when (arr[3]) {"right" -> Runnable { println("${arr[1]} is >>move>>  ${arr[2]}") }"left" -> Runnable { println("${arr[1]} is <<move<<  ${arr[2]}") }else -> Runnable { }}}return null}
}class TimeInterpreter : Interpreter {override fun interpreter(command: String, arr: List<String>): Runnable? {if (arr[0].equals("wait")) {return TimeWait(arr[1].toLong())}return null}
}object ScriptProcessor {var service = Executors.newSingleThreadExecutor();var queueAction = LinkedList<Runnable>()var scripts= arrayListOf<Interpreter>()fun load(clazz:Class<out Interpreter>):ScriptProcessor{scripts.add(clazz.newInstance())return this}fun addAction(actuin: String): ScriptProcessor = apply {addAction(change(actuin))}fun addAction(actuin: Runnable): ScriptProcessor = apply { queueAction.add(actuin) }private fun change(action: String): Runnable {try {var arr = action.split(" ")for(script in scripts) {script.interpreter(action, arr)?.apply{ return this }}return Runnable { println("无效脚本:${action}") }} catch (e: Exception) {return Runnable { println("错误脚本:${action}") }}}fun start(): ScriptProcessor = apply {while (!queueAction.isEmpty()) {service.submit(queueAction.pollFirst())}}
}fun main(args: Array<String>) {ScriptProcessor.load(SkillInterpreter::class.java).load(MoveInterpreter::class.java).load(TimeInterpreter::class.java).addAction("skill k eat").addAction("wait 2000").addAction("wait ^%$%#$%").addAction("move k 5 right").addAction("wait 2500").addAction("mov k 5 left")//<-错误脚本.addAction("wait 1000").addAction("move k 5 left").addAction("wait 1000").addAction("skill k ultimate").start()
}
输入结果
等待:2000
错误脚本:wait ^%%
k is >>move>> 5
等待:2500
无效脚本:mov k 5 left
等待:1000
k is <<move<< 5
等待:1000
k skill ultimate(╯‵□′)╯︵┴─┴
装饰者模式Decirator功能:用类的方式创建html标签
类似的例子比如文字有颜色有粗体有斜体
饮料有冰 热 加糖 加椰果等等需求
这种需要大量组合的类open class TextBlock
{constructor(){}constructor(str:String){content=str}constructor(block:TextBlock){nextBlock=block}var content: String?=nullvar nextBlock:TextBlock?=nullopen fun outPrintln():String{return content?:"";}open fun link(textBlock: TextBlock):TextBlock{textBlock.nextBlock=thisreturn textBlock}
}
class ColorTextBlock:TextBlock
{constructor(color:String){}constructor(color:String,block:TextBlock){nextBlock=block}override fun outPrintln():String{return "<font color='#FF0000'>${nextBlock?.outPrintln()}</font>";}
}
class StrongTextBlock:TextBlock
{constructor(){}constructor(block:TextBlock){nextBlock=block}override fun outPrintln():String{return "<strong>${nextBlock?.outPrintln()?:""}</strong>";}
}class SmallTextBlock:TextBlock
{constructor(){}constructor(block:TextBlock){nextBlock=block}override fun outPrintln():String{return "<small>${nextBlock?.outPrintln()}</small>";}
}fun main(args: Array<String>) {ColorTextBlock("#FF0000",SmallTextBlock(TextBlock("文字"))).run { outPrintln() }//简写TextBlock("文字").link(SmallTextBlock()).link(StrongTextBlock()).link(ColorTextBlock("#FF0000")).run { outPrintln() }
}
外观模式Facade功能:包装一层,将原本不同的很复杂的接口(支付宝sdk和微信sdk)统一成一种import SimplePay.SimplePayListen
import SimplePay.pay
import java.util.*class AliPaySdk{interface AliPayListen{fun alipayStatusListn(code:Int)}fun pay(gold:String,listen:AliPayListen){listen.alipayStatusListn(0)}
}
class WeixinPaySdk{interface WeixinPayListen{fun alipayStatusListn(errCode:String)}fun pay(gold:String,listen:WeixinPayListen){listen.alipayStatusListn("susscess")}
}
object SimplePay
{const val PAY_TYPE_ALI=1const val PAY_TYPE_WEIXIN=2var aliPay=AliPaySdk()var weixinPay=WeixinPaySdk();interface SimplePayListen{fun alipayStatusListn(payType:Int,errorCode:Int,message:String)}fun pay(payType:Int,gold:Double,listen:SimplePayListen){when(payType){PAY_TYPE_ALI->{AliPaySdk().pay(gold.toString(),object:AliPaySdk.AliPayListen{override fun alipayStatusListn(code: Int) {var message:String=when(code){0->"成功"else->"失败"}listen.alipayStatusListn(PAY_TYPE_ALI,code,message)}})}PAY_TYPE_WEIXIN->{WeixinPaySdk().pay(gold.toString(),object:WeixinPaySdk.WeixinPayListen{override fun alipayStatusListn(errCode: String) {var code=0var message:String=when(errCode){"susscess"->{"成功"}else->{code=2;"失败"}}listen.alipayStatusListn(PAY_TYPE_ALI,code,message)}})}else->{throw RuntimeException("no pay method")}}}}
fun main(args: Array<String>) {SimplePay.pay(SimplePay.PAY_TYPE_ALI,1.0, object:SimplePayListen{override fun alipayStatusListn(payType: Int, errorCode: Int, message: String) {println("error code:${errorCode}  message:${message}")}})
}
状态模式模式State功能:为每一种状态定义不同的显示和点击事件class Order{var subscribeState:State = State(1,{println("ui->subscribeState")},{println("onclick->subscribeState")})var payState:State = State(2,{println("show state->payState") },{println("onclick->payState") })var finishState:State = State(3,{ println("show state->finishState")},{ println("onclick->finishState") })var status:State=subscribeStateinner class State(val status:Int,val onRefreshUI:()->Unit,val onClick:()->Unit)fun refreshUI(){println("refreshUI")status.onRefreshUI.invoke()}fun onClick(){println("onClick")status.onClick.invoke()}
}fun main(arr:Array<String>){var order=Order()order.status=order.finishStateorder.refreshUI()order.onClick()
}
过滤器模式Filter为不同的人群筛选复杂的贷款套餐业务import java.util.*inline fun <reified T> orFilter(vararg args: (t:T)->Boolean): (t:T)->Boolean = { t: T->var isCheck = false;args.forEach { isCheck =  it.invoke(t)||isCheck }isCheck
}
inline fun <reified T> andFilter(vararg args: (t:T)->Boolean): (t:T)->Boolean = {t: T->var isCheck = true;args.forEach { isCheck =   it.invoke(t)&&isCheck}isCheck
}inline fun <reified T> filter(list: List<T>, vararg arr:(t:T)->Boolean): List<T> {var iterator = list.listIterator()var resultList = arrayListOf<T>()while (iterator.hasNext()) {var value = iterator.next()var isCheck = true;for (filter in arr) {isCheck = filter.invoke(value)if (!isCheck) {break}}if (isCheck) {resultList.add(value)}}return resultList
}data class Person(val id: Int, val name: String, val sex: Int, val loan: Int)var manFilter = {t:Person->t.sex == 1}
var womenFilter = {t:Person->t.sex == 2}
var loan5Filter = {t:Person->t.loan>=5}
var loan6Filter = {t:Person->t.loan>=6}
var idFilter = {t:Person->t.id<=5}//查询前5幸运客户或者资金大于5的男性
var planA=orFilter(andFilter(manFilter, loan5Filter), idFilter)
//查询资金大于等于5并且是男性 或者资金大于等于6的女性
var planB=andFilter(orFilter(manFilter, loan5Filter), orFilter(womenFilter, loan6Filter))fun main(args: Array<String>) {var arr = arrayListOf<Person>()for (i in 1..10) {var person = Person(i, "张" + i, 1 + Random().nextInt(2), (Random().nextInt(10)));arr.add(person)}arr.forEach{println(it)}println("=====planA=======")filter(arr, planA).forEach {println(it)}println("=====planB=======")// println("=====Lambda======")filter(arr, planB).forEach {println(it)}println("==planA and planB==")filter(arr,planA,planB).forEach {println(it)}}
输入结果
Person(id=1, name=张1, sex=1, loan=5)
Person(id=2, name=张2, sex=1, loan=3)
Person(id=3, name=张3, sex=1, loan=6)
Person(id=4, name=张4, sex=2, loan=2)
Person(id=5, name=张5, sex=1, loan=7)
Person(id=6, name=张6, sex=2, loan=8)
Person(id=7, name=张7, sex=1, loan=4)
Person(id=8, name=张8, sex=1, loan=8)
Person(id=9, name=张9, sex=1, loan=1)
Person(id=10, name=张10, sex=2, loan=7)
=====planA=======
Person(id=1, name=张1, sex=1, loan=5)
Person(id=2, name=张2, sex=1, loan=3)
Person(id=3, name=张3, sex=1, loan=6)
Person(id=4, name=张4, sex=2, loan=2)
Person(id=5, name=张5, sex=1, loan=7)
Person(id=8, name=张8, sex=1, loan=8)
=====planB=======
Person(id=3, name=张3, sex=1, loan=6)
Person(id=5, name=张5, sex=1, loan=7)
Person(id=6, name=张6, sex=2, loan=8)
Person(id=8, name=张8, sex=1, loan=8)
Person(id=10, name=张10, sex=2, loan=7)
==planA and planB==
Person(id=3, name=张3, sex=1, loan=6)
Person(id=5, name=张5, sex=1, loan=7)
Person(id=8, name=张8, sex=1, loan=8)
访问者模式Visitor功能:打印遍历当前文件夹下 mp3 或者 图片文件import java.io.File
import java.io.FileFilter
import java.util.ArrayListclass MP3FileFilter : FileFilter {override fun accept(file:java.io.File?): Boolean {fun checkFileName():Boolean{ val name = file?.getName();return name!!.endsWith(".mp3") || name!!.endsWith(".mp4")}return when(true){file?.isDirectory->falsecheckFileName()->trueelse->false}}
}
class ImgFileFilter : FileFilter {override fun accept(file:java.io.File?): Boolean {fun checkFileName():Boolean{ val name = file?.getName();return name!!.endsWith(".jpeg") || name!!.endsWith(".png")}return when(true){file?.isDirectory->falsecheckFileName()->trueelse->false}}
}fun getAllFile(baseFile: java.io.File,filter:FileFilter): List<java.io.File> = ArrayList<java.io.File>().apply {if (baseFile.isFile() || !baseFile.exists()) {return this}val files = baseFile.listFiles()for (file in files) {if (file.isDirectory()) {getAllFile(file,filter)} else {if(filter.accept(file)) {this.add(file)}}}}fun main(args: Array<String>) {println(getAllFile(java.io.File("."),MP3FileFilter()));println(getAllFile(java.io.File("."),ImgFileFilter()));
}
输入结果
[]
[]

实用精短-kotlin设计模式相关推荐

  1. 视频教程-实用通俗易懂的设计模式-软件设计

    实用通俗易懂的设计模式 15年一线项目从业经验,长期从事大型商业项目管理. 长期主导研发金融,水利行业等,大型商业项目.深入研究项目全生命周期,参与公司产品线定位,架构设计,管理协调实施项目投标方案编 ...

  2. Kotlin 设计模式及实战 (持续更新中......)

    Kotlin 设计模式及实战 "每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心.这样,你就能一次又一次地使用该方案而不必做重复劳动". Christo ...

  3. Kotlin设计模式实现之策略模式

    前言 周末抽空看了看<Head First设计模式>,准备专门写一个设计模式的专栏,每一种设计模式写一篇文章来巩固一下,当然,如果能帮到别人,是很开心的事了. 看到题目就知道第一篇写的是策 ...

  4. 【Spring Boot 实战开发】第2讲 Kotlin类型系统与空安全

    幻灯片1.png Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程.在 Java 开发领域的诸多著名框架:Spring ...

  5. 浅谈Java设计模式

    浅谈设计模式(Design Pattern) 序言: 与很多软件工程技术一样,模式起源于建筑领域,软件工程只有短短的几十年,与已经拥有几千年底蕴的建筑工程相比,后者有太多值得学习和借鉴的地方.1995 ...

  6. 轻松学习Java设计模式之责任链模式

    我们的态度是:每天进步一点点,理想终会被实现. 前言 设计模式,可能很多人都是看到代码知道怎么回事,但是离开代码再让其说出来,估计就有点含糊其词了,包括我自己在内.Android中其实用到的设计模式也 ...

  7. 23种设计模式实战!超级全,超级好懂!

    <高并发系统实战派>-- 你值得拥有 设计模式实战全集: 1,设计模式实战-工厂模式,别具匠心 2,设计模式实战-抽象工厂模式,分而治之 3,设计模式实战-单例模式,我就是天下无双 4,设 ...

  8. php设计模式学习笔记

    1.1.4.3 设计模式 设计模式,以GOF为权威的那套有23种,虽说设计模式是语言无关的,但是很多偏门的设计模式基本还是在java中用的比较多 分类 第一部分建造类,很好理解,就是实例化或者提供对象 ...

  9. 5月书讯:藏一个愿望等风来

    [小编语] 每一个林子,每一块草地上都稀稀拉拉地留下了蒲公英的身影.花开的时候她有着向日葵般灿烂的容颜,待到成熟,就随风飘散.无需用生生不息来形容,她只是早早地藏好了无数的愿望,静等风来. 这个月书少 ...

最新文章

  1. JavaScript函数与Window对象
  2. Bytomd 助记词恢复密钥体验指南
  3. MySQL锁阻塞分析
  4. The Web Audio autoplay policy will be re-enabled in 音频无法播放
  5. JavaScript~~自调用方法
  6. Cloud Connector的普通版本和Portable版本的区别
  7. java 单开程序_java生成jar包并且单进程运行的实例
  8. rs.getDate 返回类型问题
  9. springboot 初始化一个常量map_C++ 惯用法: const 常量和字面量
  10. arcmap地图与mapinfo地图的转换
  11. VirualBox安装XP_64bit+中文语言包
  12. 软件项目管理(复习)
  13. 计算机时间无法更改,电脑时间不能修改|系统时间改不了 四个解决方法
  14. 几何均数怎样用计算机算,算术均数与几何均数的意义及计算方法
  15. Problem 2 慢跑问题
  16. 云服务器aip,云服务器API接口-云服务器的使用
  17. java获取下载链接文件流并上传至OSS
  18. 学籍专业填计算机还是理工,北京理工大学珠海计算机学院2018下半年英语四级报名通知...
  19. python黑科技自动p图_自动P图神器来了,这些逆天小程序!
  20. 2008-2020年全国各省劳动生产率

热门文章

  1. 微信小程序中英文国际版
  2. 新年快乐!_新年快乐!
  3. 建议国家民政部取缔中国塑料加工工业协会
  4. 各种平台常用命令和快捷键
  5. 用MATLAB计算函数的积分
  6. Solidworks绘制外壳的边线(扫描切除)
  7. MyBatis框架的优点
  8. OSChina 周日乱弹 —— 真有猪拱白菜的事
  9. 1024Video Stitching视频拼接
  10. 逆向——Mac使用OpenSSH远程连接越狱iPhone