我的诉求是通过构建在不同的环境下使用不同的底层实现,但是我想对上层模块隐藏相应的细节。
我期望的是外层是类的正常使用方式
使用 rollupJs 构建时使用
buildEnv
变量。
class subA {
protected fn() {
console.log('subA');
}
}
class subB {
protected fn() {
console.log('subB');
}
}
const buildEnv = 'A';
function changeSubClass() {
return buildEnv === 'A' ? subA : subB;
}
const BaseClass = changeSubClass();
class MainClass extends BaseClass {
constructor() {
supper();
this.fn(); // <--- An error is reported here
}
}
// Hope to use it this way:
const clsIns = new MainClass();
一种方法是让 subA 继承 MainClass,但对于外部使用,您需要通过调用 changeSubClass 方法来获取实例:
const buildEnv = 'A';
function createSubClass() {
const Cls = buildEnv === 'A' ? subA() : subB;
return new Cls();
}
class subA extends MainClass {
protected fn() {
console.log('subA');
}
}
class subB extends MainClass {
protected fn() {
console.log('subB');
}
}
class MainClass implements ISubCls {
constructor() {
this.fn();
}
}
interface ISubCls {
fn: () => void
}
这种使用方式不是我想要的
const clsIns = createSubClass();
我认为你没有清楚地思考 OOP 方式。如果我理解正确,您正在尝试选择类的实现以根据程序运行的环境实例化它。
我建议你避免继承以支持组合。检查下面的代码:
import { makeSendMailUseCase } from "./make-email-service";
async function main() {
const sendMail = await makeSendMailUseCase();
const result = await sendMail.execute("[email protected]", "[email protected]", "testing my email", "Hello ");
if (result) console.log(' email was sent ')
else throw new Error('failed sending mail')
}
main().catch(console.log.bind(console))
在上面的代码中,应用程序想要发送一封电子邮件。为此,将实例化一个
SendMailUseCase
类,它将协调此任务。为了发送电子邮件,它需要使用外部服务。该服务将被注入到 useCase 中,为了使事情变得简单明了,工厂模式进入行动以决定将注入哪些服务以供使用。
所以主程序并不关心最后使用的是什么服务。
import { createTestAccount } from "nodemailer";
import { SendEmailUseCase } from "./sendmail-usecase";
import { ProductionEmailService } from "./real-email-service";
import { FakeEmailService } from "./fake-email-service";
import { IEmailService } from "./email-service";
export async function makeSendMailUseCase() {
const isProduction = process.env.NODE_ENV === 'production';
let emailService: IEmailService;
if (isProduction) {
const testAccount = await createTestAccount();
emailService = new ProductionEmailService(testAccount)
} else {
emailService = new FakeEmailService();
}
return new SendEmailUseCase(emailService);
}
makeSendMailUseCase 作为一个工厂提供一个全新的 SendMailUseCase 实例,根据应用程序运行的环境选择
IEmailService
的具体实现。
import { EmailFormat, IEmailService } from "./email-service";
export class SendEmailUseCase {
constructor(private emailProvider: IEmailService) { }
execute(from: string, to: string, subject: string, content: string): Promise<boolean> {
const params = {
from, to, subject, content, format: EmailFormat.Text
}
return this.emailProvider.sendEmail(params);
}
}
SendEmailUseCase
只是将发送电子邮件的任务委托给构造函数中提供的任何具体实现。
export enum EmailFormat {
Text,
HTML
}
export type EmailOptions = {
from: string;
to: string;
subject: string;
content: string;
format: EmailFormat
}
export interface IEmailService {
sendEmail(options: EmailOptions): Promise<boolean>;
}
这里是
FakeEmailService
实施:
import { EmailOptions, IEmailService } from "./email-service";
export class FakeEmailService implements IEmailService {
async sendEmail(options: EmailOptions): Promise<boolean> {
console.log(options)
return Promise.resolve(true);
}
}
这个实现只是满足合约,什么都不做,但它有效。
import { TestAccount, Transporter, createTransport } from "nodemailer";
import { EmailFormat, EmailOptions, IEmailService } from "./email-service";
export class ProductionEmailService implements IEmailService {
private readonly transporter: Transporter
constructor(account: TestAccount) {
this.transporter = createTransport({
host: account.smtp.host,
port: account.smtp.port,
secure: account.smtp.secure,
auth: {
user: account.user, // generated ethereal user
pass: account.pass, // generated ethereal password
},
});
}
async sendEmail(options: EmailOptions): Promise<boolean> {
const info = await this.transporter.sendMail({
from: options.from,
to: options.to,
subject: options.subject,
text: options.format === EmailFormat.Text ? options.content : "",
html: options.format === EmailFormat.HTML ? options.content : "",
});
return info;
}
}
真正的生产电子邮件服务依赖于另一个图书馆,
nodemailer
最终交付电子邮件。
就是这样!
// Import
// prettier-ignore
interface Logger { info: (msg: string) => void }
// prettier-ignore
class ConsoleLogger implements Logger { info(msg: string): void { console.log("[Console]:", msg) } }
// prettier-ignore
class PinoLogger implements Logger { info(msg: string): void { console.log("[Pino]:" , msg) } }
// Part 1: Business Entities
interface UserData {
name: string
}
class AuthService {
async getUserData(): Promise<UserData> {
return { name: "Big Lebowski" }
}
}
class User {
constructor(private data: UserData) {}
name = () => this.data.name
}
class PaymentService {
constructor(private readonly logger: Logger, private readonly user: User) {}
sendMoney() {
this.logger.info(`Sending monery to the: ${this.user.name()} `)
return true
}
}
// Step 2: Manual DI / composition root
export async function runMyApp() {
const logger =
process.env.NODE_ENV === "production"
? new PinoLogger()
: new ConsoleLogger()
const auth = new AuthService()
const user = new User(await auth.getUserData())
const paymentService = new PaymentService(logger, user)
paymentService.sendMoney()
}
console.log(" ---- My App START \n\n")
runMyApp().then(() => {
console.log("\n\n ---- My App END")
})