Foreword
Uncle Bob proposed and carried forward the five principles of S.O.L.I.D to better implement object-oriented programming. The five principles are:
The Single Responsibility Principle (Single Responsibility SRP)
The Open/ Closed Principle (OCP)
The Liskov Substitution Principle (LSP)
The Interface Segregation Principle (ISP)
The Dependency Inversion Principle (DIP)
I believe the five principles have been discussed in the blog community, especially the implementation of C#, but compared to JavaScript, a prototype-based dynamic type language, there are still few. This series will be divided into 5 articles. Demonstrates the application of the five principles based on the JavaScript programming language. OK, let’s start our first article: single responsibility.
English original text: http://freshbrewedcode.com/derekgreer/2011/12/08/solid-javascript-single-responsibility-principle/
Single responsibility
Description of single responsibility As follows:
A class should have only one reason to change
There should be only one reason for a class change
Copy code
A class (should be an object under JavaScript) should have a close set of What does the relevant behavior mean? The advantage of adhering to a single responsibility is that it allows us to easily maintain this object. When an object encapsulates many responsibilities, once one responsibility needs to be modified, it will inevitably affect the code of other responsibilities of the object. Through decoupling, each responsible worker can be made more flexible to change.
However, how do we know that multiple behaviors of an object construct multiple responsibilities or a single responsibility? We can decide by referring to the concept of Role Stereotypes proposed in the book Object Design: Roles, Responsibilies, and Collaborations, which proposes the following Role Stereotypes to distinguish responsibilities:
Information holder – This object is designed to store objects and Provide object information to other objects.
Structurer - This object is designed to maintain the relationship between objects and information
Service provider - This object is designed to process work and provide services to other objects
Controller - This object is designed to control a series of responsible decisions Task Processing
Coordinator - This object does not do any decision-making processing work, it just delegates work to other objects
Interfacer - This object is designed to transform information (or requests) in various parts of the system
Once you know With these concepts, it is easy to know whether your code has multiple responsibilities or a single responsibility.
Example code
This example code demonstrates adding items to the shopping cart. The code is very bad. The code is as follows:
function Product(id, description) {
this.getId = function () {
return id;
};
this.getDescription = function () {
return description;
};
}
function Cart(eventAggregator) {
var items = [];
this.addItem = function (item) {
items.push(item);
};
}
(function () {
var products = [new Product (1, "Star Wars Lego Ship"),
new Product(2, "Barbie Doll"),
new Product(3, "Remote Control Airplane")],
cart = new Cart() ;
function addToCart() {
var productId = $(this).attr('id');
var product = $.grep(products, function (x) {
return x.getId() == productId;
})[0];
cart.addItem(product);
var newItem = $('
').html(product.getDescription()).attr('id-cart', product.getId()).appendTo("#cart");
}
products.forEach(function (product) {
var newItem = $('').html(product.getDescription())
.attr('id', product.getId())
.dblclick(addToCart)
.appendTo("#products");
});
})();
This code declares 2 functions respectively Used to describe products and carts, and the responsibility of the anonymous function is to update the screen and interact with the user. This is not a very complex example, but the anonymous function contains many unrelated responsibilities. Let's take a look at how many responsibilities there are. :
First, there is a declaration of the product collection
Secondly, there is a code that binds the product collection to the #product element, and also attaches an event handler for adding to the shopping cart
Third, there is the function of displaying the Cart shopping cart
Fourth, there is the function of adding product items to the shopping cart and displaying them
Refactoring the code
Let us break it down so that the codes can be stored in their own objects. , To this end, we refer to the event aggregator (Event Aggregator) theory of martinfowler to process the code to communicate between objects.
First of all, let’s implement the event aggregation function. This function is divided into two parts, one is Event, which is used for Handler callback code, and the other is EventAggregator, which is used to subscribe and publish Events. The code is as follows:
function Event(name) {
var handlers = [];
this.getName = function () {
return name;
};
this.addHandler = function (handler) {
handlers.push(handler);
};
this.removeHandler = function (handler) {
for (var i = 0; i < handlers.length; i ) {
if (handlers[i] == handler) {
handlers.splice(i, 1);
break;
}
}
};
this.fire = function (eventArgs) {
handlers.forEach(function (h) {
h(eventArgs);
});
};
}
function EventAggregator() {
var events = [];
function getEvent(eventName) {
return $.grep(events, function (event) {
return event.getName() === eventName;
})[0];
}
this.publish = function (eventName, eventArgs) {
var event = getEvent(eventName);
if (!event) {
event = new Event(eventName);
events.push(event);
}
event.fire(eventArgs);
};
this.subscribe = function (eventName, handler) {
var event = getEvent(eventName);
if (!event) {
event = new Event(eventName);
events.push(event);
}
event.addHandler(handler);
};
}
然后,我们来声明Product对象,代码如下:
function Product(id, description) {
this.getId = function () {
return id;
};
this.getDescription = function () {
return description;
};
}
接着来声明Cart对象,该对象的addItem的function里我们要触发发布一个事件itemAdded,然后将item作为参数传进去。
function Cart(eventAggregator) {
var items = [];
this.addItem = function (item) {
items.push(item);
eventAggregator.publish("itemAdded", item);
};
}
CartController主要是接受cart对象和事件聚合器,通过订阅itemAdded来增加一个li元素节点,通过订阅productSelected事件来添加product。
function CartController(cart, eventAggregator) {
eventAggregator.subscribe("itemAdded", function (eventArgs) {
var newItem = $('
').html(eventArgs.getDescription()).attr('id-cart', eventArgs.getId()).appendTo("#cart");
});
eventAggregator.subscribe("productSelected", function (eventArgs) {
cart.addItem(eventArgs.product);
});
}
Repository的目的是为了获取数据(可以从ajax里获取),然后暴露get数据的方法。
function ProductRepository() {
var products = [new Product(1, "Star Wars Lego Ship"),
new Product(2, "Barbie Doll"),
new Product(3, "Remote Control Airplane")];
this.getProducts = function () {
return products;
}
}
ProductController里定义了一个onProductSelect方法,主要是发布触发productSelected事件,forEach主要是用于绑定数据到产品列表上,代码如下:
function ProductController(eventAggregator, productRepository) {
var products = productRepository.getProducts();
function onProductSelected() {
var productId = $(this).attr('id');
var product = $.grep(products, function (x) {
return x.getId() == productId;
})[0];
eventAggregator.publish("productSelected", {
product: product
});
}
products.forEach(function (product) {
var newItem = $('
').html(product.getDescription())
.attr('id', product.getId())
.dblclick(onProductSelected)
.appendTo("#products");
});
}
最后声明匿名函数:
(function () {
var eventAggregator = new EventAggregator(),
cart = new Cart(eventAggregator),
cartController = new CartController(cart, eventAggregator),
productRepository = new ProductRepository(),
productController = new ProductController(eventAggregator, productRepository);
})();
You can see that the code of the anonymous function has been reduced a lot, mainly one The instantiation code of the object. In the code, we introduced the concept of Controller, which receives information and then passes it to the action. We also introduced the concept of Repository, which is mainly used to handle the display of products. The result of refactoring is to write a lot of Object declaration, but the advantage is that each object has its own clear responsibilities. The display data should be displayed, and the processing collection should be changed to process the collection, so the degree of coupling is very low.
Final code
function Event(name ) {
var handlers = [];
this.getName = function () {
return name;
};
this.addHandler = function (handler) {
handlers.push(handler);
};
this.removeHandler = function (handler) {
for (var i = 0; i < handlers.length; i ) {
if (handlers[i] == handler) {
handlers.splice(i, 1);
break;
}
}
};
this.fire = function (eventArgs) {
handlers.forEach(function (h) {
h(eventArgs);
});
};
}
function EventAggregator() {
var events = [];
function getEvent(eventName) {
return $.grep(events, function (event) {
return event.getName( ) === eventName;
})[0];
}
this.publish = function (eventName, eventArgs) {
var event = getEvent(eventName);
if (!event) {
event = new Event(eventName);
events.push(event);
}
event.fire(eventArgs);
};
this.subscribe = function (eventName, handler) {
var event = getEvent(eventName);
if (!event) {
event = new Event(eventName);
events.push(event);
}
event.addHandler(handler);
};
}
function Product(id, description) {
this.getId = function () {
return id;
};
this.getDescription = function () {
return description;
};
}
function Cart(eventAggregator) {
var items = [];
this.addItem = function (item) {
items.push(item);
eventAggregator.publish ("itemAdded", item);
};
}
function CartController(cart, eventAggregator) {
eventAggregator.subscribe("itemAdded", function (eventArgs) {
var newItem = $('
').html(eventArgs.getDescription()).attr('id-cart', eventArgs.getId()).appendTo("#cart");
});
eventAggregator.subscribe("productSelected", function (eventArgs) {
cart.addItem(eventArgs.product);
});
}
function ProductRepository() {
var products = [new Product(1, "Star Wars Lego Ship"),
new Product(2, "Barbie Doll"),
new Product(3, "Remote Control Airplane")];
this.getProducts = function () {
return products;
}
}
function ProductController(eventAggregator, productRepository) {
var products = productRepository.getProducts();
function onProductSelected() {
var productId = $(this).attr('id');
var product = $.grep (products, function (x) {
return x.getId() == productId;
})[0];
eventAggregator.publish("productSelected", {
product: product
});
}
products.forEach(function (product) {
var newItem = $('').html(product.getDescription( ))
.attr('id', product.getId())
.dblclick(onProductSelected)
.appendTo("#products");
});
}
(function () {
var eventAggregator = new EventAggregator(),
cart = new Cart(eventAggregator),
cartController = new CartController(cart, eventAggregator),
productRepository = new ProductRepository(),
productController = new ProductController(eventAggregator, productRepository);
})();
Summary
Seeing this refactoring result, some bloggers may want to I asked, is it really necessary to make it so complicated? All I can say is: whether you should do this or not depends on the circumstances of your project.
If your project is a very small project and there is not a lot of code, there is actually no need to refactor it so complicatedly, but if your project is a very complex large-scale project, or your small project If it may grow very fast in the future, you must consider the SRP principle for separation of responsibilities in the early stage, so as to facilitate future maintenance.