Fundamentals 7 min read

When to Choose Composition Over Inheritance: Practical Guidelines from a Real Project

The article explains why the guideline “favor composition over inheritance” is useful but not absolute, illustrates a fixed‑workflow scenario where inheritance simplifies implementation, and provides concrete code examples and decision criteria for choosing between the two approaches.

samdeepthink
samdeepthink
samdeepthink
When to Choose Composition Over Inheritance: Practical Guidelines from a Real Project
The principle “favor composition over inheritance” is correct, but it is only a guideline, not an absolute rule.

Many developers see similar code and instinctively extract a parent class, citing lower coupling, avoidance of fragile base‑class problems, and better encapsulation as advantages of composition. However, the author presents a scenario where inheritance becomes the more natural engineering choice.

The business has a fixed workflow that only allows variations in certain steps.

In a DingTalk OA work‑flow system the core operations are three: create an OA work‑order, cancel an OA work‑order, and handle DingTalk callbacks. There are six different work‑order types (e.g., replenishment order, store‑time‑change approval, product‑sold‑out approval) that share the same fixed process but differ in form data and post‑approval handling. Because the process is stable and the variations are limited, inheritance is a natural fit.

One might argue for a composition approach—splitting the logic into validators, form assemblers, creators, etc., and composing them per work‑order type. The author initially considered this, but realized that each implementation class would still need to manage the order of steps, risking missed validations or incorrect sequencing that code reviews might not catch.

The solution is to lock the workflow in an abstract superclass AbstractDingTalkWorkFlowStrategy. The core createWorkFlow method is fixed as follows:

// Validate → Assemble basic info → Assemble form → Create
this.checkCanCreateWorkFlow(createWorkFlowCommand.getBizId());
CreateWorkFlowDTO dto = this.assembledBaseInfo(createWorkFlowCommand);
this.assembledFormComponents(dto);
this.doCreateWorkFlow(dto);

Subclasses can only affect the two abstract variation points declared in the superclass:

protected abstract void assembledFormComponents(CreateWorkFlowDTO createWorkFlowDTO);
protected abstract void approvalCompleted(DingTalkWorkFlowRecordEntity entity);

For example, ReplenishmentOrderWorkFlowStrategy implements assembledFormComponents to build the replenishment form and approvalCompleted to send a DingTalk notification after approval. ShopTimeChangeApplyWorkFlowStrategy assembles a store‑time‑change form and updates the store system in approvalCompleted. All six subclasses share the fixed logic for canceling and handling callbacks, avoiding code duplication.

The method checkCanCreateWorkFlow is empty in the superclass and overridden only where needed (e.g., the replenishment subclass checks for duplicate applications or limit violations). This demonstrates that extension points remain within the locked workflow, preventing accidental omission.

Composition is more suitable when you need to organize a set of independent capabilities—such as payment channels (Alipay, WeChat, UnionPay) or notification channels (DingTalk, SMS, email)—that can be freely mixed into any business logic.

Inheritance shines when a workflow is fixed and any future change must affect all implementations simultaneously. Adding a new permission check in the superclass automatically applies to all six approval types, whereas a composition solution would require each implementation to add the check manually, increasing the risk of missed updates.

In summary, the author decides based on two questions: Are you dealing with independent abilities or a stable process with several variation points? Will the logic evolve together across all implementations? Use composition for the former and inheritance (often via the Template Method pattern) for the latter. This reasoning can also be useful in interview discussions.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

design patternssoftware designOOPtemplate methodinheritancecomposition
samdeepthink
Written by

samdeepthink

Knowledge Planet: Old Dock's Tech Chronicles Zhihu: SamDeepThinking A technical manager who still codes heavily on the front line. From junior developer to tech lead, then tech manager, now leading the whole front‑ and back‑end development team—leveling up along the way. I have some insights on programming, career development, and tech management.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.