继承 不可重用_与对象组合继承成角度的代码重用

继承 不可重用

Based on my story Inheritance & Object Composition I want to show you, how you can use some Angular features for reusing code in really nice ways. I prepared some easy examples below.

根据我的故事《继承与对象组成》,我想向您展示如何使用一些Angular功能以非常好的方式重用代码。 我在下面准备了一些简单的示例。

遗产 (Inheritance)

Inheritance is a very simple principle in Angular. You can combine a base class with a superclass by using the extends term. The superclass inherits fields and methods from the base class.

继承是Angular中非常简单的原则。 您可以使用扩展项将基类与超类组合。 超类从基类继承字段和方法。

Subscribing and unsubscribing to an observable in a Redux store is a common repeatable practise. You can DRY, when you separate this logic in a base class.

订阅和取消订阅Redux存储中的可观察对象是一种常见的可重复实践。 当您在基类中分离此逻辑时,可以使用DRY

>>> base.component.tsexport class BaseComponent implements OnInit, OnDestroy {
public randomNumber: number;
private selectNumber$: Observable<number>;
private subNumber: Subscription;

private destroyer$: Subject<void>;

constructor(protected store: Store<State>) {
this.destroyer$ = new Subject<void>();
}

ngOnInit(): void {
this.subscribeToRandomNumber();
}

ngOnDestroy(): void {
this.unsubscribeNumber();
}

public getRandomNumber() {
this.store.dispatch(GetRandomNumber());
}

protected subscribeToRandomNumber(): void {
this.selectNumber$ = this.store.select(selectRandomNumber);
this.subNumber = this.selectNumber$
.pipe(takeUntil(this.destroyer$))
.subscribe((next: number) => {
this.randomNumber = next;
});
}

/*
@deprecated, because of this
.destroyer$
*/
protected
unsubscribeNumber(): void {
if (this.subNumber) {
this.subNumber.unsubscribe();
}
}
}

Subclasses, which want to use declared stored properties too, can use the base class subscription for consumption.

子类也要使用声明的存储属性,可以使用基类订阅进行使用。

>>> sub.component.tsexport class SubComponent extends BaseComponent implements OnInit, OnDestroy {

constructor(protected store: Store<State>) {
super(store);
}

ngOnInit(): void {
super.ngOnInit();
}

ngOnDestroy() {
super.ngOnDestroy();
}
}
>>> sub.component.html<div>
Random number: {{randomNumber}}
<button (click)="getRandomNumber()">Get random number</button>
</div>

As you can see, the base class can be reused now by subclasses that need to consume the random number of the Redux store. You can extend this example by adding more properties to the appState and by creating some base classes to subscribe to. You don’t need to change the old ones, because of the encapsulation of the components.

如您所见,需要消耗随机数的Redux存储的子类现在可以重用基类。 您可以通过向appState添加更多属性并创建一些要订阅的基类来扩展此示例。 由于组件的封装,您不需要更改旧的

Image for post
UML 2.5
UML 2.5

服务组成 (Composition with services)

In his article Dependency Inversion Netanel Nasal wrote about service reuse with object composition in Angular.

Netanel Nasal在他的文章Dependency Inversion中写道 有关在Angular中使用对象组合进行服务重用的信息。

You can combine an abstract service implementation with a concrete service class by using the providers tag in the configuration metadata section where you declare your component. After that, you need to use the abstract class for the service injection in the constructor. Now there is a decoupled composition with services.

您可以使用声明组件的配置元数据部分中的providers标记将抽象服务实现与具体服务类结合起来。 之后,您需要使用抽象类在构造函数中进行服务注入。 现在,服务与服务之间的关系已经脱钩。

Example for configuration metadata:

配置元数据示例:

@Component({
selector: 'app-dark-theme',
templateUrl: './dark-theme.component.html',
styleUrls: ['./dark-theme.component.scss'],
providers: [{
provide: AbstractThemeService,
useClass: DarkThemeService
}]
})

Firstly, you need an abstract class of a service.

首先,您需要服务的抽象类。

export abstract class AbstractThemeService {
abstract getFontName(): string;
abstract getStyleName(): string;
}

Secondly, concrete classes (services) need to implement the abstract class.

其次,具体的类(服务)需要实现抽象类。

@Injectable()
export class LightThemeService implements AbstractThemeService {

getFontName(): string {
return 'Comic Sans MS';
}

getStyleName(): string {
return 'light';
}
}@Injectable()
export class DarkThemeService implements AbstractThemeService {

getFontName(): string {
return 'Arial';
}

getStyleName(): string {
return 'dark';
}
}

Lastly, you can reuse the code by using Dependency Inversion and Angular’s provider tag in components.

最后,您可以使用Dependency Inversion和Angular的provider标记重用代码 在组件中

@Component({
selector: 'app-light-theme',
templateUrl: './light-theme.component.html',
styleUrls: ['./light-theme.component.scss'],
providers: [{
provide: AbstractThemeService,
useClass: LightThemeService
}]
})
export class LightThemeComponent implements OnInit {

public font: string;
public style: string;

constructor(private themeService: AbstractThemeService) { }

ngOnInit(): void {
this.font = this.themeService.getFontName(); // 'Comic Sans MS'
this.style = this.themeService.getStyleName(); // 'light'
}
}@Component({
selector: 'app-dark-theme',
templateUrl: './dark-theme.component.html',
styleUrls: ['./dark-theme.component.scss'],
providers: [{
provide: AbstractThemeService,
useClass: DarkThemeService
}]
})
export class DarkThemeComponent implements OnInit {public font: string;
public style: string;

constructor(private themeService: AbstractThemeService) { }

ngOnInit(): void {
this.font = this.themeService.getFontName(); // 'Arial'
this.style = this.themeService.getStyleName(); // 'dark'
}
}

Additionally, you can create further theme services that implement the abstract class and switch the useClass term in the section where your component is defined without changing any other code.

此外,您可以创建其他实现抽象类的主题服务,并在定义组件的部分中切换useClass术语,而无需更改任何其他代码。

Also, you can put the internals of the theme components into a base class and inherit them to the light- or dark-theme component to be even more DRY. Take a look at my GitHub example at the end of the article.

另外,您可以将主题组件的内部放入基类中,并将它们继承给亮主题或暗主题组件,以使其更加DRY 。 看一下本文结尾处的GitHub示例。

Image for post
UML 2.5
UML 2.5

与可观察的成分 (Composition with observables)

In Angular, using observables is a nice concept for sharing information with other modules, components, services, and more. You can combine object composition with observables to make your code more flexible, sustainable, and reusable.

在Angular中,使用可观察对象是与其他模块,组件,服务等共享信息的好方法。 您可以将对象组成与可观察对象结合使用,以使您的代码更加灵活,可持续和可重用。

In an example of a supermarket context, there are many products with the same properties, e.g. weight, size, cost, and many more. To abstract them, you can create an interface or an abstract class and define the properties there.

在超级市场的​​示例中,有许多具有相同属性(例如重量,尺寸,成本等)的产品。 要抽象它们,您可以创建一个接口或一个抽象类,然后在其中定义属性。

export interface IProduct {
getName(): string;
getPrice(): number;
getWeight(): number;
}

After that you concretise some classes, for example, IceCream or Juice that implement the IProduct interface.

之后,您将实现IProduct接口的一些类具体化,例如IceCreamJuice

export class IceCream implements IProduct {
private price: number;
private weight: number;

constructor() {
this.price = 1.30;
this.weight = 100;
}

public getPrice(): number {
return this.price;
}
public getWeight(): number {
return this.weight;
}

public getName(): string {
return 'Ice cream';
}
}export class Juice implements IProduct {

public getPrice(): number {
return 15.23 - 7.30;
}
public getWeight(): number {
return 8.00 - 3.33;
}

public getName(): string {
return 'Juice';
} ...
}

In order to store these products from a supermarket component into a cart component, a service with a subject is required. A getProduct() function can return an observable with the interface IProduct you defined above. An addToCart() function expects an IProduct interface as a parameter. So, you are using Dependency Inversion again. At runtime, you will create for example a juice object that implements the IProduct, and use it in both functions.

为了将这些产品从超级市场组件存储到购物车组件中,需要提供主题服务。 getProduct()函数可以使用上面定义的IProduct接口返回一个observable。 addToCart()函数需要IProduct接口作为参数。 因此,您再次使用了Dependency Inversion 。 在运行时,您将创建例如实现IProduct的juice对象,并在两个函数中都使用它。

@Injectable({providedIn: 'root'})
export class CartService {
private subject = new Subject<IProduct>();

public addToCart(product: IProduct): void {
this.subject.next(product);
}

public getProduct(): Observable<IProduct> {
return this.subject.asObservable();
}

public clearCart(): void {
this.subject.next();
}
}

To grab some products to fill the cart, you need to define a simple supermarket component.

要抓取一些产品填充购物车,您需要定义一个简单的超级市场组件。

@Component({
selector: 'app-supermarket',
template: '<h2>Supermarket</h2>\n' +
'<button (click)="btnAddIceCream()">Add ice cream</button>\n' +
'<button (click)="btnAddPizza()">Add pizza</button>\n' +
'<button (click)="btnAddJuice()">Add juice</button>',
styleUrls: ['./supermarket.component.scss']
})
export class SupermarketComponent {

constructor(private cartService: CartService) { }

public btnAddIceCream() {
this.addToCart(new IceCream());
}

public btnAddPizza() {
this.addToCart(new Pizza());
}

public btnAddJuice() {
this.addToCart(new Juice());
}

public addToCart(product: IProduct): void {
this.cartService.addToCart(product);
}
}

Now you can subscribe to this observable with a shopping cart component.

现在,您可以通过购物车组件订阅此可观察的内容。

@Component({
selector: 'app-cart',
template: '' +
'<button (click)="btnClear()">Clear cart</button>\n' +
'<div *ngFor="let product of cart">\n' +
' - {{product.getName()}}, Price: {{product.getPrice()}} Euro, {{product.getWeight()}}g\n' +
'</div>\n',
styleUrls: ['./cart.component.scss']
})
export class CartComponent implements OnInit, OnDestroy {

public cart: IProduct[];

private subShoppingCart: Subscription;
private onDestroyer$: Subject<void>;

constructor(private cartService: CartService) {
this.cart = [];
this.onDestroyer$ = new Subject<void>();
}

ngOnInit(): void {
this.subShoppingCart = this.cartService.getProduct()
.pipe(takeUntil(this.onDestroyer$))
.subscribe((next: IProduct) => {
if (next) {
this.cart.push(next);
} else {
this.cart = [];
}
});
}

ngOnDestroy() {
this.onDestroyer$.next();
this.onDestroyer$.complete();
}

public btnClear(): void {
this.cartService.clearCart();
}
}

The service can share different products with the observable, because it’s implemented with an interface (very abstract) IProduct.

该服务可以与可观察的对象共享不同的产品,因为它是通过IProduct接口(非常抽象)实现的

If you want to add more products to the supermarket, you don’t need to modify the service or the cart component. The only thing you need to add is a new model and an extension to the supermarket component. Object composition and Dependency Inversion make this possible.

如果要向超市添加更多产品,则无需修改服务或购物车组件。 您唯一需要添加的就是新模型和超级市场组件的扩展。 对象组成和依赖反转使之成为可能。

Image for post
UML 2.5
UML 2.5

组成和装饰 (Composition and decorators)

The Input() and Output() decorators are useful tools for sharing information between components. You can combine them with object composition to be more decoupled and encapsulated. And of course, you are using Dependency Inversion again.

Input()Output()装饰器是在组件之间共享信息的有用工具。 您可以将它们与对象组成结合使用,以进一步分离和封装。 当然,您将再次使用Dependency Inversion

Let’s code a snack bar with some ice cream on their menu.

让我们编写一个在菜单上加些冰淇淋的小吃店代码。

First of all, you create an interface (abstract class) and some models (concrete classes) that implement the interface:

首先,创建一个接口(抽象类)和一些实现该接口的模型(具体类):

export interface IceCream {
getPrice(): number;
getType(): string;
getCalories(): number;
}export class Vanilla implements IceCream {

public getPrice(): number {
return 1.10;
}

getType(): string {
return 'Vanilla';
}

getCalories(): number {
return 100;
}
}export class Strawberry implements IceCream {

public getPrice(): number {
return 1.00;
}

getType(): string {
return 'Strawberry';
}

getCalories(): number {
return 98;
}
}

The snack bar component has got some get()-functions to create the products and return them as an interface type:

小吃店组件具有一些get()函数来创建产品并将其作为接口类型返回:

export class ImbissComponent {

public getVanilla(): IceCream {
return new Vanilla();
}

public getStrawberry(): IceCream {
return new Strawberry();
}

public getStracciatella(): IceCream {
return new Stracciatella();
}
}

View:

视图:

<app-ice-cream-card [iceCream]="getStracciatella()"></app-ice-cream-card><br>
<app-ice-cream-card [iceCream]="getVanilla()"></app-ice-cream-card><br>
<app-ice-cream-card [iceCream]="getStrawberry()"></app-ice-cream-card>

The ice cream component got the Input() decorator to receive some ice cream models and show them in the view. That’s the point with the magic and Dependency Inversion, because you pass the concrete classes as interfaces.

冰淇淋组件获得了Input()装饰器,以接收一些冰淇淋模型并将其显示在视图中。 这就是魔术和依赖倒置的意义所在,因为您将具体的类作为接口传递。

export class IceCreamCardComponent {
@Input() iceCream: IceCream;
}

View:

视图:

Type: {{this.ice.getType()}}
Price: {{this.ice.getPrice() }}
Calories: {{this.ice.getCalories()}}

Now you can create more and more ice cream types without changing the ice cream card component — nice reuse of the card.

现在,您可以创建越来越多的冰淇淋类型,而无需更改冰淇淋卡组件-卡的良好重用。

Image for post
UML 2.5
UML 2.5

奖励:服务中的组成 (Bonus: composition in a service)

Using object composition and Dependency Inversion in conjunction with service parameters is a common way to reuse code in Angular. You can extend and reuse your application code without changing the logic of the service very easily.

将对象组合和依赖关系反转与服务参数一起使用是在Angular中重用代码的常用方法。 您可以扩展和重用您的应用程序代码,而无需非常轻松地更改服务的逻辑。

Concurrently, you use the principle of Open-Closed.

同时,您使用Open-Closed原理。

First of all, you need an interface and some concrete classes for this example.

首先,此示例需要一个接口和一些具体的类。

export interface Color {
paint(): string;
}export class Purple implements Color {
public paint(): string {
return 'purple';
}
}export class Red implements Color {
public paint(): string {
return 'red';
}
}

A service uses the interface as a parameter and calls the paint() function.

服务将接口用作参数,并调用paint()函数。

@Injectable({
providedIn: 'root'
})
export class ColorService {

public paint(color: Color): string {
return color.paint();
}
}

In a concrete component you create instances of the concrete classes and use Dependency Inversion in conjunction with the service.

在具体组件中,您将创建具体类的实例,并将Dependency Inversion与服务结合使用。

export class PaletteComponent {

private color: Color;

constructor(private colorService: ColorService) { }

public useRed(): void {
this.color = new Red();
}

public usePurple(): void {
this.color = new Purple();
}

public useYellow(): void {
this.color = new Yellow();
}

public paint(): void {
console.log( this.colorService.paint(this.color));
}

So, you can add more models of colors without changing our service. Nice code reuse!

因此,您可以添加更多颜色模型,而无需更改我们的服务。 很好的代码重用!

Image for post
UML 2.5
UML 2.5

结论 (Conclusion)

As you can see, it’s easy to reuse code with inheritance, object composition, and Dependency Inversion. I presented a small number of examples and I think, there are more possibilities in Angular for code reuse with inheritance and object composition.

如您所见,通过继承,对象组成和Dependency Inversion可以很容易地重用代码。 我提供了一些示例,我认为Angular在继承和对象组合方面有更多的代码重用可能性。

These principles are common ways to make your code more testable, because you can easily mock data. Try it out!

这些原则是使代码更具可测试性的常用方法,因为您可以轻松地模拟数据。 试试看!

Thanks for reading and happy coding! :)

感谢您的阅读和愉快的编码! :)

Github上的代码示例 (Code example on Github)

学到更多(Learn More)

资源资源(Resources)

翻译自: https://medium.com/@itchimonji/code-reuse-in-angular-with-object-composition-inheritance-c7194631e522

继承 不可重用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值