Do you ever such an experience, a project, what modularity, what abstraction, decoupling ah, what what what reusable components, which high-end use which, however, in the middle of the project development to with the increase of module, reusable, just work, what modularity, load, over time, the project is more and more big, It is also becoming more and more bloated, more and more difficult to maintain, to change a seemingly simple module, but found that the eight poles can not hit the place was actually affected, it is really cool when writing, more cool when maintaining!
That project is big, maintenance has become a problem, how to optimize it, how to solve it!
IOC (InversionofControl)
Look at the abbreviation, isn’t it a bit lofty, in fact this idea is very common in the back end, and the front end is rarely involved. But the modern front end can also be implemented in projects, and it fits nicely.
Three criteria
- High-level modules should not depend on low-level modules, they should all depend on abstractions
- Abstraction should not depend on concrete implementation; concrete implementation should depend on abstraction
- Programming for interfaces rather than for implementations
A case study
Put these guidelines do not say, we are familiar with the macbook case to illustrate it! As we all know, the MAC is made up of modules that look like this when translated into code:
// screen.ts
export default class Screen {
name = "Retina";
}
// cpu.ts
export default class Cpu {
name = "i5";
}
// battery
// Battery mode, normal mode, low battery, high battery
type TMode = "normal" | "low" | "high";
export default class Battery {
mode: string;
constructor(option: { mode: TMode } = { mode: "normal"{})this.mode = option.mode; }}// mac.ts
import Screen from "./screen";
import Cpu from "./cpu";
import Battery from "./battery";
export default class MacBook {
cpu: Cpu;
screen: Screen;
battery: Battery;
constructor() {
this.cpu = new Cpu();
this.screen = new Screen();
this.battery = new Battery();
}
start() {
console.log(
`your mac screen is battery mode is The ${this.battery.mode}, screen is The ${this.screen.name
} and cpu is The ${this.cpu.name}`); }}// index.ts
import MacBook from "./mac";
let mac = new MacBook();
mac.start();
Copy the code
Ts startup file, MAC shell mac.ts. It has three modules inside, CPU, Screen and battery. These three attributes refer to modules outside the file respectively.
If I want to set the MAC battery configuration mode to low power, I will have to go to the MAC. Ts main module to change the battery configuration.
this.battery = new Battery({mode: "low"});
Copy the code
In fact, there is no problem with this change. However, if one of the modules of MAC is modified, why should the file of shell Mac.ts be moved? Besides, this shell has all the module dependencies of MAC. So this module change is the problem I mentioned above, how to change?
First optimization
// mac.ts
import Screen from "./screen";
import Cpu from "./cpu";
import Battery from "./battery";
interface IMac {
cpu: Cpu;
screen: Screen;
battery: Battery;
}
export default class MacBook {
cpu: Cpu;
screen: Screen;
battery: Battery;
constructor(option: IMac) {
this.cpu = option.cpu;
this.screen = option.screen;
this.battery = option.battery;
}
start() {
console.log(
`your mac screen is battery mode is The ${this.battery.mode}, screen is The ${this.screen.name
} and cpu is The ${this.cpu.name}`); }}// index.ts
import MacBook from "./mac";
import Battery from "./battery";
import Cpu from "./cpu";
import Screen from "./screen";
let mac = new MacBook({
cpu: new Cpu(),
screen: new Screen(),
battery: new Battery()
});
mac.start();
Copy the code
All module dependencies are placed in the startup file index.ts. No matter how the module is changed, the shell module Mac. ts does not need to be changed, and the coupling degree between modules is also reduced.
In short, Mac. TS is a high-level module and battery.ts is a low-level module. Before optimization, Mac. TS relies on battery.ts, does it violate the first principle of IOC? There is a technical term for this approach – Dependency Injection.
The type of argument you need to pass inIMac
This is the defined abstract shell modulemac.ts
Is dependent on this abstraction, and this abstraction is not dependent on a concrete implementation.
If I want to add a touchpad module to the MAC instance, then I need to modify the shell module mac.ts. Is there no general solution?
Second optimization
// mac.ts
type IModule<T> = T | T[];
export default class MacBook {
private modules: any[];
use<T>(module: IModule<T>) {
Array.isArray(module)?module.map(item= > this.use(item))
: this.modules.push(module);
return this;
}
start() {
console.log(this.modules); }}// index.ts
import MacBook from "./mac";
import Battery from "./battery";
import Cpu from "./cpu";
import Screen from "./screen";
import Touchpad from "./touchpad";
let mac = new MacBook();
mac
.use(new Cpu())
.use(new Screen())
.use([new Battery({mode: "high"}), new Touchpad()])
.start();
Copy the code
The use method of koA loading modules can be chained, so that the shell module Mac.ts is completely decoupled from the lower level module, and it will not change no matter how many new modules are added to the MAC. There is no business code in Mac.ts anymore, and all the configuration is in the outermost layer, making it easy to modify and add.
Third optimization
So the question again, MAC. Ts to a module has asked, however, is not any brand of modules can be installed on my MAC, according to certain standards is to perform, which is according to certain conventions, this is the third criterion, programming to an interface, not an implementation, with the code below to show the principles:
// mac.ts
type IModule<T> = T | T[];
export default class MacBook {
private modules: any[] = [];
use<T>(module: IModule<T>) {
Array.isArray(module)?module.map(item= > this.use(item))
: this.modules.push(module);
return this;
}
start() {
this.modules.map(
module= >
module.init && typeof module.init === "function" && module.init() ); }}Copy the code
Mac.ts: init () : init () : init ();
// cpu.ts
export default class Cpu {
name = "i5";
init() {
console.log(`The ${this.name} start`); }}Copy the code
Similarly, to interconnect with the shell, you have to implement an init method inside the module so that the module can work inside the shell.
The init method is just an abstract method for mac.ts, a convention interface that hands out the implementation to the various modules it interconnects with.
conclusion
In fact, mac.TS should be called a Container in IOC terms, which is closer to reality and easier to understand. It is not related to business implementation, but only performs some initialization operations. Therefore, the shell should not change with the change of the module it depends on. Therefore, an IOC programming philosophy is needed to optimize it, and dependency injection is just an implementation of this philosophy.
Finally, thinking is the best way to improve your programming, not learning how to use a framework!
The original address