如何成为一个更好的程序员?设计原则I:KISS,DRY...( 二 )

在此示例中 , 我们没有使用每天手动输入的方式来实现带有预定义日期名称的枚举 , 并且还引入了Day类 。因此 , 我们可以扩展它以在将来向此类添加更多功能 , 例如getDaylightTime 。此外 , 我们还为Week类实现了addDay方法 , 该方法的作用几乎相同 , 但是现在 , 如果发生任何更改 , 我们只需在代码中更新一个位置即可 , 而不是更新七个位置 。
这是DRY原则 。
TDA:告知而不要询问该原则建议我们应该以一种使对象行为而不是对象处于何种状态的方式来编写代码 。这可以避免类之间不必要的依赖关系 , 这要归功于它们具有更易于维护的代码 。它与代码封装紧密相关 。
没有TDA原理的代码示例:
class User {_id: string = '';firstName: string = '';lastName: string = '';tokens: number = 0;}class UserService {register(firstName: string, lastName: string): User {if ( firstName.length < 3 ) {throw new Error("Name is not long enough.");}if ( lastName.length < 3) {throw new Error("Name is not long enough");}const user = new User();user._id = Math.random().toString(36).substring(7);user.firstName = firstName.toLowerCase();user.lastName = lastName.toLowerCase();return user;}updateTokens(user: User, operation: string, amount: number): User {if (operation == 'add') {user.tokens += amount;}if (operation == 'sub') {if (user.tokens - amount >= 0 ) {user.tokens -= amount;} else {user.tokens = 0;}}return user}}const uService = new UserService();const u = uService.register("John", "Smith");uService.updateTokens(u, 'add', 1000);console.log( u );uService.updateTokens(u, 'sub', 1100);console.log( u );正如我们可以看到UserService多次访问User对象属性一样 , 特别是在更新User.tokens时 , 如果我们在程序的许多部分中都拥有该功能并且想要更改其工作方式的逻辑呢 , 还要看看验证器:所有逻辑 它的行为方式在方法内部 , 但是我们应该使其更具可伸缩性和可重用性 。下面是一个示例如何执行此操作 。
TDA示例:
/** * VALIDATORS */class StringLengthValidator {static greaterThan(value: string, length: number): boolean {if ( value.length > length){return true} else {throw new Error("String is not long enough.");}}}class NaturalNumberValidator {static operation(from: number, amount: number) {if (from + amount <= 0) {return 0;}return from + amount;}}/** * INTERFACES */interface IUserAccount {_id: string;firstName: string;lastName: string;tokens: number;}/** * ENUMS */enum operations {add = 'add',sub = 'sub'}/** * CLASSES */class User implements IUserAccount {_id : string = '';firstName: string = '';lastName: string = '';tokens: number = 0;constructor(firstName, lastName) {this._id = this._generateRandomID();this.setFirstName(firstName);this.setLastName(lastName);}setFirstName(newFirstName: string): User {StringLengthValidator.greaterThan(newFirstName, 3);this.firstName = newFirstName;return this;}setLastName(newLastName: string): User {StringLengthValidator.greaterThan(newLastName, 3);this.lastName = newLastName;return this;}updateTokens(amount: number): User {this.tokens = NaturalNumberValidator.operation(this.tokens, amount);return this;}private _generateRandomID() {return Math.random().toString(36).substring(7);}}class UserService {register(firstName: string, lastName: string): User {let user : User = null;try {user = new User(firstName, lastName);} catch (e) {console.log(e);}return user;}updateTokens(user: User, operation: operations, amount: number): User {if (operation === operations.sub) {amount *= -1;}return user.updateTokens(amount);}}/** * PROGRAM */const uService = new UserService();const u = uService.register("john", "smith");uService.updateTokens(u, operations.add, 1000);console.log(u);uService.updateTokens(u, operations.sub, 1100);console.log(u);乍一看 , 似乎似乎过于复杂 , 需要更多代码 。但是 , 长远来说 , 感谢封装和独立的验证器 , 我们可以在许多通用情况下多次使用它 。User类的属性仅在其内部使用 , UserService正在调用高级方法来访问它 。因此 , 我们将所有逻辑都放在一个地方 , 因此当我们要在其他地方使用User类时 , 程序将按预期运行 。
SoC:关注点分离这个原则告诉我们将一个班级的责任划分给这个班级和仅将这个班级的责任分开 。对象不应共享其功能 。每个类都应该是唯一的 , 并且应与其他类分开 。
不带SoC的代码示例:
class User {_id: string = '';name: string = '';balance: number = 0;constructor(name) {this._id = Math.random().toString(36).substring(7);this.name = name;}}class AccountService {log(msg: string) {console.log((new Date()) + " :: " + msg);}transfer(user1: User, user2: User, amount: number): any {// validate amountif ( amount <= 0 ){this.log("amount 0, nothing changed.");return {user1, user2};}// validate if user1 have enoughif ((user1.balance - amount) < 0) {this.log("user " + user1._id + " did not have enough funds.");return {user1, user2};}//get from user1user1.balance -= amount;// add to user2user2.balance += amount;this.log("User " + user1._id + " now has " + user1.balance);this.log("User " + user2._id + " now has " + user2.balance);return {user1, user2};}updateBalance(user: User, amount: number): User {user.balance += amount;this.log("User " + user._id + " now has " + user.balance);return user;}}const aService = new AccountService();let u1 = new User("john");u1 = aService.updateBalance(u1, 1000);let u2 = new User("bob");u2 = aService.updateBalance(u2, 500);console.log( aService.transfer(u1, u2, 250) );


推荐阅读