可靠的原理是罗伯特·C·鲍勃·马丁叔叔提出的一套软件设计。这些原则指导开发人员构建强大的可维护应用程序,同时最大程度地减少变化的成本。
尽管经常将固体原理与面向对象的编程一起使用,但我们可以将它们与其他语言(如JavaScript)一起使用。在本文中,我们将讨论如何在JavaScript中使用坚实的原理并用代码示例演示它们。
有什么坚实的原则?
- Single responsibility principle
- Open-closed principle
- Liskov substitution principle
- Interface segregation principle
- Dependency inversion principle
Single responsibility principle
类,模块或函数应仅负责一个演员。因此,它应该有一个也是唯一的改变。
单一责任原则是最简单的原则之一。但是,开发人员经常误解它,认为模块应该做一个事情。
让我们考虑一个简单的例子来理解这一原则。以下JavaScript代码片段具有名为 ManagyEmployee 的类,以及几个可以管理员工的功能。
class ManageEmployee {
constructor(private http: HttpClient)
SERVER_URL = 'http://localhost:5000/employee';
getEmployee (empId){
return this.http.get(this.SERVER_URL + `/${empId}`);
}
updateEmployee (employee){
return this.http.put(this.SERVER_URL + `/${employee.id}`,employee);
}
deleteEmployee (empId){
return this.http.delete(this.SERVER_URL + `/${empId}`);
}
calculateEmployeeSalary (empId, workingHours){
var employee = this.http.get(this.SERVER_URL + `/${empId}`);
return employee.rate * workingHours;
}
}
以前的代码一目了然,似乎完全不错,许多开发人员将遵循同样的方法而没有任何问题。但是,由于这是两个演员的原因,因此该班级违反了单一责任原则。 getEmployee(), UpdateEmployee()和 deleteemployee()函数与HR管理直接相关,而 calculate employemployeesalary() 功能与财务管理有关。
将来,如果您需要更新人力资源或财务部门的功能,则必须更改 Manage Employeee 类,从而影响两个参与者。因此,管理人员类违反了单一责任原则。您需要将与人力资源和财务部门相关的功能分开,以使代码与单个职责原则兼容。以下代码示例说明了这一点。
class ManageEmployee {
constructor(private http: HttpClient)
SERVER_URL = 'http://localhost:5000/employee';
getEmployee (empId){
return this.http.get(this.SERVER_URL + `/${empId}`);
}
updateEmployee (employee){
return this.http.put(this.SERVER_URL + `/${employee.id}`,employee);
}
deleteEmployee (empId){
return this.http.delete(this.SERVER_URL + `/${empId}`);
}
}
class ManageSalaries {
constructor(private http: HttpClient)
SERVER_URL = 'http://localhost:5000/employee';
calculateEmployeeSalary (empId, workingHours){
var employee = this.http.get(this.SERVER_URL + `/${empId}`);
return employee.rate * workingHours;
}
}
Open-closed principle
功能,模块和类应可扩展,但不能修改。
这是实施大规模应用程序时要遵循的重要原则。根据这一原则,我们应该能够轻松地为应用程序添加新功能,但是我们不应引入现有代码的破坏更改。
例如,假设我们已经实现了一个名为 CounculatesAlaries()的函数,该功能使用具有定义的工作角色和小时费率来计算薪水的数组。
class ManageSalaries {
constructor() {
this.salaryRates = [
{ id: 1, role: 'developer', rate: 100 },
{ id: 2, role: 'architect', rate: 200 },
{ id: 3, role: 'manager', rate: 300 },
];
}
calculateSalaries(empId, hoursWorked) {
let salaryObject = this.salaryRates.find((o) => o.id === empId);
return hoursWorked * salaryObject.rate;
}
}
const mgtSalary = new ManageSalaries();
console.log("Salary : ", mgtSalary.calculateSalaries(1, 100));
直接修改薪金数组将违反开放原理。例如,假设您需要扩展新角色的工资计算。在这种情况下,您需要创建一种单独的方法,以在工资rates 数组中添加工资率,而无需制作原始代码。
class ManageSalaries {
constructor() {
this.salaryRates = [
{ id: 1, role: 'developer', rate: 100 },
{ id: 2, role: 'architect', rate: 200 },
{ id: 3, role: 'manager', rate: 300 },
];
}
calculateSalaries(empId, hoursWorked) {
let salaryObject = this.salaryRates.find((o) => o.id === empId);
return hoursWorked * salaryObject.rate;
}
addSalaryRate(id, role, rate) {
this.salaryRates.push({ id: id, role: role, rate: rate });
}
}
const mgtSalary = new ManageSalaries();
mgtSalary.addSalaryRate(4, 'developer', 250);
console.log('Salary : ', mgtSalary.calculateSalaries(4, 100));
Liskov substitution principle
令P( y )为A类型A的对象 y 。对于B类型的对象 x ,其中b是A。
的子类型您会在整个互联网上找到Liskov替代原则的不同定义,但它们都意味着相同的含义。简单地说,Liskov原则指出,如果他们在应用程序中创建意外行为,我们不应用其子类替换。
例如,考虑一个名为动物的类
class Animal{
eat() {
console.log("Animal Eats")
}
}
现在,我将把动物类扩展到名为 bird 的新类,其功能名为 fly()。
class Bird extends Animal{
fly() {
console.log("Bird Flies")
}
}
var parrot = new Bird();
parrot.eat();
parrot.fly();
在上一个示例中,我创建了一个名为鹦鹉的对象, bird class,并称为 eat()和 fly()方法。由于鹦鹉都能够采用这两种动作,因此将动物类扩展到 bird 类不违反Liskov原则。
现在让我们进一步扩展 bird 级别,并创建一个名为 ostrich的新类。
class Ostrich extends Bird{
console.log("Ostriches Do Not Fly")
}
var ostrich = new Ostrich();
ostrich.eat();
ostrich.fly();
鸟类的这种扩展类违反了liskov原理,因为鸵鸟不能飞行 - 这可能会在应用程序中产生意外的行为。解决此案例的最佳方法是将 ostrich 类延长 Animal 类。
class Ostrich extends Animal{
walk() {
console.log("Ostrich Walks")
}
}
Interface segregation principle
不应将客户推向他们永远不会使用的接口。
该原理与接口有关,并着重于将大界面分解为较小的接口。例如,假设您要开车去学习如何驾驶汽车,它们为您提供了有关驾驶汽车,卡车和火车的大量说明。由于您只需要学习驾驶汽车,因此您不需要所有其他信息。驾驶学校应分配说明,只向您提供特定于汽车的说明。
由于JavaScript不支持接口,因此很难在基于JavaScript的应用程序中采用此原理。但是,我们可以使用JavaScript组成来实施此功能。组成使开发人员可以在不继承整个类的情况下向类添加功能。例如,假设有一个名为 drive Trive -Test 的类,其两个功能命名 startCartest 和 startTruckTest。 CardRivingTest 和 TruckDrivingTest的strong> class 我们必须强迫两个类以实施 startcartest 和 startTrucktest 功能。
Class DrivingTest {
constructor(userType) {
this.userType = userType;
}
startCarTest() {
console.log(“This is for Car Drivers”’);
}
startTruckTest() {
console.log(“This is for Truck Drivers”);
}
}
class CarDrivingTest extends DrivingTest {
constructor(userType) {
super(userType);
}
startCarTest() {
return “Car Test Started”;
}
startTruckTest() {
return null;
}
}
class TruckDrivingTest extends DrivingTest {
constructor(userType) {
super(userType);
}
startCarTest() {
return null;
}
startTruckTest() {
return “Truck Test Started”;
}
}
const carTest = new CarDrivingTest(carDriver );
console.log(carTest.startCarTest());
console.log(carTest.startTruckTest());
const truckTest = new TruckDrivingTest( ruckdriver );
console.log(truckTest.startCarTest());
console.log(truckTest.startTruckTest());
但是,此实现违反了接口隔离原则,因为我们强迫两个扩展类实施这两个功能。我们可以通过使用组合来为所需类附加功能来解决此问题。
Class DrivingTest {
constructor(userType) {
this.userType = userType;
}
}
class CarDrivingTest extends DrivingTest {
constructor(userType) {
super(userType);
}
}
class TruckDrivingTest extends DrivingTest {
constructor(userType) {
super(userType);
}
}
const carUserTests = {
startCarTest() {
return ‘Car Test Started’;
},
};
const truckUserTests = {
startTruckTest() {
return ‘Truck Test Started’;
},
};
Object.assign(CarDrivingTest.prototype, carUserTests);
Object.assign(TruckDrivingTest.prototype, truckUserTests);
const carTest = new CarDrivingTest(carDriver );
console.log(carTest.startCarTest());
console.log(carTest.startTruckTest()); // Will throw an exception
const truckTest = new TruckDrivingTest( ruckdriver );
console.log(truckTest.startTruckTest());
console.log(truckTest.startCarTest()); // Will throw an exception
现在, cartest.starttrucktest(); 将引发例外,因为 startTruckTest()函数未分配给 cardRivingTest 类。
Dependency inversion principle
高级模块应使用抽象。但是,它们不应依赖低级模块。
依赖性反转是关于解解代码的。遵循此原则将使您灵活地扩展和更改最高级别的应用程序。
关于JavaScript,我们不需要考虑抽象,因为JavaScript是一种动态语言。但是,我们需要确保更高级别的模块不依赖于低级模块。
让我们考虑一个简单的例子来解释依赖性反转的工作方式。假设您在应用程序中使用了Yahoo电子邮件API,现在您需要将其更改为Gmail API。如果您在没有依赖性反转的情况下实现了控制器,则需要对控制器进行一些更改。这是因为多个控制器使用Yahoo API,您需要找到每个实例并进行更新。
class EmailController {
sendEmail(emailDetails) {
// Need to change this line in every controller that uses YahooAPI.const response = YahooAPI.sendEmail(emailDetails);
if (response.status == 200) {
return true;
} else {
return false;
}
}
}
依赖性反转原则可以通过将电子邮件API处理零件移动到单独的控制器中,从而帮助开发人员避免此类昂贵的错误。然后,只有在电子邮件API中发生更改时,您才需要更改该控制器。
class EmailController {
sendEmail(emailDetails) {
const response = EmailApiController.sendEmail(emailDetails);
if (response.status == 200) {
return true;
} else {
return false;
}
}
}
class EmailApiController {
sendEmail(emailDetails) {
// Only need to change this controller. return YahooAPI.sendEmail(emailDetails);
}
}
结论
在本文中,我们讨论了坚实的原理在软件设计中的重要性,以及如何在JavaScript应用程序中采用这些概念。作为开发人员,必须在我们的应用中理解和使用这些核心概念。有时,在使用小型应用程序时,这些原则的好处可能并不明显,但是您一定会知道,一旦您开始从事大型项目,它们就会产生的不同。
我希望这篇文章能帮助您了解扎实的原则。谢谢您的阅读。
Syncfusion JavaScript suiteis构建应用程序所需的唯一套件。它包含一个包装中的80多个高性能,轻巧,模块化和响应性UI组件。下载free trial并立即评估控件。
如果您有任何疑问或评论,则可以通过我们的support forums,support portal或feedback portal与我们联系。我们总是很乐意为您提供帮助!