欢迎来到设计模式与编程范式站点,这里专门探索基于各种语言的设计模式与编程范式,力求打造最佳编程实践。
https://github.com/microwind/design-patterns
https://github.com/microwind/design-patterns/tree/main/design-principles
https://github.com/microwind/design-patterns/tree/main/programming-paradigm
https://github.com/microwind/design-patterns/tree/main/mvx
// Model即业务实体对象,可以增添少许方法来处理数据
// src/mvc/Model.java
public class Model {
private String name;
private Integer num;
public Model() {
}
public Model(String name, Integer num) {
this.name = name;
this.num = num;
}
public String getName() {
return this.name;
}
public Integer getNum() {
return this.num;
}
}
// Processing bussiness logic,业务逻辑处理,组织与处理Model,此类可选
// src/mvc/ModelService.java
import java.util.Optional;
public class ModelService {
Model model;
public Optional<Model> getModel() {
if (this.model == null) {
this.model = new Model("Thomas", 10);
}
// do something
return Optional.of(this.model);
}
}
// Controller,负责控制转发和模板映射。采用WebServlet注解,也可通过web.xml配置
// src/mvc/Controller.java
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(
name = "ModelServlet",
urlPatterns = "/hello-model")
public class Controller extends HttpServlet {
private ModelService modelService = new ModelService();
private void processRequest(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
modelService.getModel().ifPresent(s -> {
request.setAttribute("helloModel", s);
});
RequestDispatcher dispatcher = request.getRequestDispatcher(
"/WEB-INF/template/model.jsp");
dispatcher.forward(request, response);
}
@Override
protected void doGet(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected void doPost(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
}
// /WEB-INF/template/model.jsp
// View,使用JSP
<%@ page import="mvc.Model" %>
<html>
<head>
<title>Hello Model</title>
</head>
<body>
<h1>Hello Model</h1>
<%
if (request.getAttribute("helloModel") != null) {
System.out.print(new Model());
Model model = (Model) request.getAttribute("helloModel");
%>
<div>Name: <%= model.getName()%></div>
<div>Num: <%= model.getNum()%></div>
<% } %>
</body>
</html>
// Java MVC版结束
!(function(win, doc) {
// View
class View {
constructor($el, tpl) {
this.$el = $el
this.tpl = tpl
}
render(data) {
this.$el.innerHTML = this.compile(data)
}
compile(data) {
return this.tpl || `<div><b>${data.name}</b>
<em>${data.num}</em></div>`
}
}
// Model
class Model {
constructor() {
this.data = { name: 'MVC Basic', num: 1 }
}
update(value, cb) {
const fn = (res) => {
this.data.num += value
cb(this.data)
}
fetch('./').then(fn).catch(fn)
}
change(name, cb) {
this.data.name = name
cb(this.data)
}
}
// Controller
class Controller {
constructor(view, model) {
this.view = view
this.model = model
}
init() {
this.view.$el.addEventListener('click', evt => {
this.model.data.name = 'Click to add:Event dispatch.'
this.model.update(1, data => this.view.render(data))
})
this.view.$el.addEventListener('mouseout', evt => {
this.model.change('Click to add', data => this.view.render(data))
})
this.view.render(this.model.data)
}
}
const model = new Model()
const view = new View(doc.querySelector('#mvc-basic-view'))
const controller = new Controller(view, model)
controller.init()
})(window, document)
!(function(win, doc) {
class Observer {
constructor() {
this.observers = []
}
register(callback) {
this.observers.push(callback)
}
notify(args) {
for (let i = 0, l = this.observers.length; i < l; i++) {
this.observers[i](args)
}
}
}
// View
class View {
constructor($el, model, tpl) {
this.$el = $el
this.model = model
this.tpl = tpl
this.onAdd = new Observer(this)
this.onChange = new Observer(this)
this.model.onUpdate.register(value => this.render())
this.model.onChange.register(name => this.render())
this.$el.addEventListener('click', evt => this.onAdd.notify(1))
this.$el.addEventListener('mouseout', evt => this.onChange.notify('Click to add'))
}
render() {
this.$el.innerHTML = this.compile(this.model.data)
}
compile(data) {
return this.tpl || `<div><b>${data.name}</b>
<em>${data.num}</em></div>`
}
}
// Model
class Model {
constructor() {
this.data = { name: 'MVC', num: 1 }
this.onUpdate = new Observer(this)
this.onChange = new Observer(this)
}
update(value) {
const fn = (res) => {
this.data.num += value
this.onUpdate.notify(value)
}
fetch('./').then(fn).catch(fn)
}
change(name) {
this.data.name = name
this.onChange.notify(name)
}
}
// Controller
class Controller {
constructor(view, model) {
this.view = view
this.model = model
}
init() {
this.view.onAdd.register(value => {
this.model.data.name = 'Click to add:Event dispatch.'
this.model.update(value)
})
this.view.onChange.register(name => this.model.change(name))
this.view.render()
}
}
const model = new Model()
const view = new View(doc.querySelector('#mvc-view'), model)
const controller = new Controller(view, model)
controller.init()
})(window, document)
!(function(win, doc) {
// View
class View {
constructor($el, tpl) {
this.$el = $el
this.tpl = tpl
}
render(data) {
this.$el.innerHTML = this.compile(data)
}
compile(data) {
return this.tpl || `<div><b>${data.name}</b>
<em>${data.num}</em></div>`
}
}
// Model
class Model {
constructor() {
this.data = { name: 'MVP', num: 1 }
}
update(value) {
const fn = (res) => {
this.data.num += value
return res
}
return fetch('./').then(fn).catch(fn)
}
change(name) {
this.data.name = name
}
}
// Presenter
class Presenter {
constructor(view, model) {
this.view = view
this.model = model
view.$el.addEventListener('click', evt => this.add(1))
view.$el.addEventListener('mouseout', evt => this.change('Click to add'))
}
init() {
this.render(this.model.data)
}
render(data) {
this.view.render(data)
}
add(value) {
this.model.data.name = 'Click to add:Event proxy.'
this.model.update(value).then(res => {
this.render(this.model.data)
})
}
change(name) {
this.model.change(name)
this.render(this.model.data)
}
}
const view = new View(doc.querySelector('#mvp-view'))
const presenter = new Presenter(view, new Model())
presenter.init()
})(window, document)
!(function(win, doc) {
class Observer {
constructor() {
this.observers = []
}
register(callback) {
this.observers.push(callback)
}
notify(args) {
for (let i = 0, l = this.observers.length; i < l; i++) {
this.observers[i](args)
}
}
}
// View
class View {
constructor($el, tpl) {
this.$el = $el
this.tpl = tpl
this.onAdd = new Observer(this)
this.onChange = new Observer(this)
this.$el.addEventListener('click', evt => this.onAdd.notify(1) )
this.$el.addEventListener('mouseout', evt => this.onChange.notify('Click to add'))
}
render(data) {
this.$el.innerHTML = this.compile(data)
}
compile(data) {
return this.tpl || `<div><b>${data.name}</b>
<em>${data.num}</em></div>`
}
}
// Model
class Model {
constructor() {
this.data = { name: 'MVP+Observer', num: 1 }
this.onUpdate = new Observer(this)
this.onChange = new Observer(this)
}
update(value) {
const fn = (res) => {
this.data.num += value
this.onUpdate.notify(value)
return res
}
return fetch('./').then(fn).catch(fn)
}
change(name) {
this.data.name = name
this.onChange.notify(name)
}
}
// Presenter
class Presenter {
constructor(view, model) {
this.view = view
this.model = model
this.view.onAdd.register(value => this.add(value))
this.view.onChange.register(name => this.change(name))
this.model.onUpdate.register(res => this.render())
this.model.onChange.register(res => this.render())
}
init() {
this.render()
}
render() {
this.view.render(this.model.data)
}
add(value) {
this.model.data.name = 'Click to add:Event proxy.'
this.model.update(value)
}
change(name) {
this.model.change(name)
}
}
const $el = doc.querySelector('#mvp-observer-view')
const presenter = new Presenter(new View($el), new Model())
presenter.init()
})(window, document)
!(function(win, doc) {
class Observer {
constructor() {
this.observers = []
}
register(callback) {
this.observers.push(callback)
}
notify(args) {
for (let i = 0, l = this.observers.length; i < l; i++) {
this.observers[i](args)
}
}
}
// View
class View {
constructor($el, tpl) {
this.$el = $el
this.tpl = tpl
this.onAdd = new Observer(this)
this.onChange = new Observer(this)
}
render(data) {
this.$el.innerHTML = this.compile(data)
}
compile(data) {
return this.tpl || `<div><b>${data.name}</b>
<em>${data.num}</em></div>`
}
}
// Model
class Model {
constructor() {
this.data = { name: 'MVP+Dispatch', num: 1 }
this.onUpdate = new Observer(this)
this.onChange = new Observer(this)
}
update(value) {
const fn = (res) => {
this.data.num += value
this.onUpdate.notify(value)
}
fetch('./').then(fn).catch(fn)
}
change(name) {
this.data.name = name
this.onChange.notify(name)
}
}
// Controller
class Controller {
constructor(view, model) {
this.view = view
this.model = model
}
init() {
this.view.$el.addEventListener('click', evt => this.view.onAdd.notify(1))
this.view.$el.addEventListener('mouseout', evt => this.view.onChange.notify('Click to add'))
this.view.onAdd.register(value => {
this.model.data.name = 'Click to add:Event dispatch.'
this.model.update(value)
})
this.view.onChange.register(name => this.model.change(name))
this.model.onUpdate.register(value => this.view.render(this.model.data))
this.model.onChange.register(name => this.view.render(this.model.data))
this.view.render(this.model.data)
}
}
const model = new Model()
const view = new View(doc.querySelector('#mvp-dispatch-view'))
const controller = new Controller(view, model)
controller.init()
})
!(function(win, doc) {
class Agent {
constructor(target, watch) {
const handle = {
set(obj, prop, value) {
if (obj[prop] !== value && obj.hasOwnProperty(prop)) {
obj[prop] = value
watch && watch(obj, prop, value)
}
return Reflect.set(obj, prop, value)
},
get(obj, prop) {
if (typeof obj[prop] === 'object' && obj[prop] !== null) {
return new Proxy(obj[prop], handle)
} else {
return Reflect.get(obj, prop)
}
}
}
return new Proxy(target, handle)
}
}
// View
class View {
constructor($el, tpl) {
this.$el = $el
}
compile(data) {
return this.tpl || `
<div><b>${data.name}</b>
<em>${data.num}</em></div>`
}
render(data) {
this.$el.innerHTML = this.compile(data)
}
}
// Model
class Model {
constructor() {
this.data = { name: 'MVVM', num: 1 }
}
update(data) {
const fn = (res) => {
data.num += 1
return data
}
return fetch('./').then(fn).catch(fn)
}
}
// Controller
class ViewModel {
constructor({ $el, view, model }) {
this.view = view
this.data = new Agent(model.data, (obj, prop, value) => {
this.view.render(this.data)
})
this.model = model
this.init()
}
init() {
console.log('render:', this.data)
this.view.$el.addEventListener('click', (evt) => this.add(evt))
this.view.$el.addEventListener('mouseout', (evt) => this.change(evt))
this.view.render(this.data)
}
change(evt) {
this.data.name = 'Click to add'
}
add(evt) {
this.data.name = 'Click to add:Watch data.'
this.model.update(this.data)
}
}
const viewModel = new ViewModel({
view: new View(doc.querySelector('#mvvm-view')),
model: new Model()
})
})(window, document)
!(function(win, doc) {
class Observer {
constructor(vm) {
this.vm = vm
this.observers = []
}
register(callback) {
this.observers.push(callback)
}
notify(args) {
for (let i = 0, l = this.observers.length; i < l; i++) {
this.observers[i](args)
}
}
}
class Watcher {
constructor(vm, cb) {
this.vm = vm
this.cb = cb
return this.run.bind(this)
}
run(obj, prop, value) {
console.log('watch:', arguments)
if (this.vm.watch[prop]) {
(this.vm.watch[prop]).call(this.vm.data, value, this.vm)
}
this.cb.call(this.vm, obj, prop, value, this)
}
}
class Agent {
constructor(target, observer) {
const handle = {
set(obj, prop, value) {
if (obj[prop] !== value && obj.hasOwnProperty(prop)) {
obj[prop] = value
observer && observer.notify(obj, prop, value)
}
return Reflect.set(obj, prop, value)
},
get(obj, prop) {
if (typeof obj[prop] === 'object' && obj[prop] !== null) {
return new Proxy(obj[prop], handle)
} else {
return Reflect.get(obj, prop)
}
}
}
return new Proxy(target, handle)
}
}
function template(strings, ...keys) {
return function(...values) {
const dict = values[values.length - 1] || {}
const result = [strings[0]]
keys.forEach((key, i) => {
let value = dict[key]
result.push(value, strings[i + 1])
})
return result.join('')
}
}
// View
class View {
constructor($el, tpl) {
this.$el = $el
this.tpl = tpl
this.events = []
}
compile(data) {
const content = this.tpl(data)
let reg = new RegExp('e-([a-z]+)\=\"([a-z0-9]+)' + '\"', 'igm')
const result = content.match(reg) || []
result.forEach((item) => {
const [evt, fn] = item.split('=')
this.events.push([evt.substr(2), fn.substr(0, fn.length - 1).substr(1)])
})
return content
}
render(data) {
this.$el.innerHTML = this.compile(data)
}
}
const Model = { name: 'Tiny MVVM Framework', num: 1 }
// ViewModel
class TinyMVVM {
constructor({ $el, tpl, data, methods = {}, watch = {}, computed = {} }) {
this.view = new View($el, tpl)
this.observer = new Observer(this)
this.watcher = new Watcher(this, (obj, prop, value) => {
this.render()
})
this.observer.register(this.watcher)
this.data = new Agent(data(), this.observer)
this.methods = methods
this.watch = watch
this.computed = computed
this.init()
}
render() {
const data = Object.assign({}, this.data)
for (let attr in this.computed) {
data[attr] = (this.computed[attr]).call(this.data, this)
}
this.view.render(data)
}
init() {
this.render()
this.mount()
}
mount() {
this.view.events.forEach((item) => {
this.view.$el.addEventListener(item[0], (evt) => (this.methods[item[1]].bind(this))(evt))
})
}
}
new TinyMVVM({
$el: doc.querySelector('#mvvm-watch-view'),
tpl: template`
<div e-click="add" e-mouseout="change"><b>${'name'}</b>
<em>${'num'}</em> | ${'computedProp'}</div>`,
data() {
return Model
},
methods: {
fetch(value) {
const fn = (res) => {
this.data.num += value
return this.data
}
return fetch('./').then(fn).catch(fn)
},
change(evt) {
this.data.name = 'Click to add'
},
add(evt) {
this.data.name = 'Click to add:Watch data.'
this.methods.fetch.bind(this)(1)
}
},
computed: {
computedProp: function(vm) {
return this.name + ' computed.'
}
},
watch: {
num(value, vm) {
this.name = 'Click to add:Watch data.'
}
}
})
})(window, document)