如何创建孙类排列组合?基于音频引擎Sound类的子类扩展需求
Great question! Since Java doesn’t support multiple inheritance, combining the features of LoopSound and MultiSound into a "grandchild" class requires some intentional design. Below are three practical approaches tailored to your audio engine, along with code examples and tradeoffs for each:
1. Extract Features to Interfaces (Flexible Base for Combinations)
First, abstract the unique behaviors of LoopSound and MultiSound into interfaces. This lets you mix-and-match features in any combination by having concrete classes implement multiple interfaces while inheriting from Sound.
Step 1: Define Interfaces
// Interface for loop-capable sounds public interface Loopable { void setLoopCount(int count); void startContinuousLoop(); } // Interface for multi-instance capable sounds public interface MultiPlayable { void playNewInstance(); void stopAllInstances(); }
Step 2: Refactor Existing Subclasses
Update your original subclasses to implement these interfaces (keeping their core logic):
public class LoopSound extends Sound implements Loopable { private int loopCount; public LoopSound(String filePath) { super(filePath); } @Override public void setLoopCount(int count) { this.loopCount = count; if (clip != null) { clip.loop(count); } } @Override public void startContinuousLoop() { setLoopCount(Clip.LOOP_CONTINUOUSLY); play(); } } public class MultiSound extends Sound implements MultiPlayable { private List<Clip> activeClips = new ArrayList<>(); public MultiSound(String filePath) { super(filePath); } @Override public void playNewInstance() { Clip newClip = createNewClip(filePath); activeClips.add(newClip); newClip.start(); } @Override public void stopAllInstances() { activeClips.forEach(Clip::stop); activeClips.clear(); } // Helper to create new Clip instances private Clip createNewClip(String filePath) { // Implement audio loading logic here return null; } }
Step 3: Create Combined "Grandchild" Class
Now build a class that inherits from Sound and implements both interfaces, combining their logic:
public class LoopMultiSound extends Sound implements Loopable, MultiPlayable { private int loopCount; private List<Clip> activeClips = new ArrayList<>(); public LoopMultiSound(String filePath) { super(filePath); } // Implement Loopable methods @Override public void setLoopCount(int count) { this.loopCount = count; } @Override public void startContinuousLoop() { setLoopCount(Clip.LOOP_CONTINUOUSLY); playNewInstance(); } // Implement MultiPlayable methods with loop logic @Override public void playNewInstance() { Clip newClip = createNewClip(filePath); newClip.loop(loopCount); activeClips.add(newClip); newClip.start(); } @Override public void stopAllInstances() { activeClips.forEach(Clip::stop); activeClips.clear(); } private Clip createNewClip(String filePath) { // Reuse or implement clip creation logic return null; } }
Pros: Highly flexible—easily add new features (like VolumeControllable) with new interfaces.
Cons: Minor code duplication across implementing classes (fixable with helper utility classes).
2. Use the Decorator Pattern (Dynamic Runtime Combinations)
If you need to mix features dynamically (e.g., turn a regular Sound into a loopable multi-instance sound at runtime), the decorator pattern is ideal. It lets you wrap Sound objects with feature-enhancing decorators.
Step 1: Create a Decorator Base Class
public abstract class SoundDecorator extends Sound { protected Sound wrappedSound; public SoundDecorator(Sound wrappedSound) { super(wrappedSound.filePath); this.wrappedSound = wrappedSound; } // Delegate basic play/stop to the wrapped sound @Override public void play() { wrappedSound.play(); } @Override public void stop() { wrappedSound.stop(); } }
Step 2: Build Feature Decorators
// Loop feature decorator public class LoopDecorator extends SoundDecorator implements Loopable { private int loopCount; public LoopDecorator(Sound wrappedSound) { super(wrappedSound); } @Override public void setLoopCount(int count) { this.loopCount = count; if (wrappedSound.clip != null) { wrappedSound.clip.loop(count); } } @Override public void startContinuousLoop() { setLoopCount(Clip.LOOP_CONTINUOUSLY); play(); } } // Multi-instance feature decorator public class MultiDecorator extends SoundDecorator implements MultiPlayable { private List<Clip> activeClips = new ArrayList<>(); public MultiDecorator(Sound wrappedSound) { super(wrappedSound); } @Override public void playNewInstance() { Clip newClip = createNewClip(wrappedSound.filePath); activeClips.add(newClip); newClip.start(); } @Override public void stopAllInstances() { activeClips.forEach(Clip::stop); activeClips.clear(); } private Clip createNewClip(String filePath) { // Implement clip creation logic return null; } }
Step 3: Combine Decorators at Runtime
// Create a base sound Sound baseExplosion = new Sound("explosion.wav"); // Wrap it to add loop + multi-instance features Sound loopMultiExplosion = new MultiDecorator(new LoopDecorator(baseExplosion)); // Use the combined features ((Loopable) loopMultiExplosion).startContinuousLoop(); ((MultiPlayable) loopMultiExplosion).playNewInstance();
Pros: Dynamic combinations—no need to predefine every possible "grandchild" class.
Cons: Requires casting to access interface methods (unless you add helper methods in decorators).
3. Create Concrete Combined Subclasses (Simple for Fixed Combinations)
If you only need a small, fixed set of combinations (like just LoopMultiSound), directly extend one subclass and integrate the other's logic.
public class LoopMultiSound extends MultiSound implements Loopable { private int loopCount; public LoopMultiSound(String filePath) { super(filePath); } @Override public void setLoopCount(int count) { this.loopCount = count; } @Override public void startContinuousLoop() { setLoopCount(Clip.LOOP_CONTINUOUSLY); playNewInstance(); } // Override playNewInstance to apply loop logic @Override public void playNewInstance() { Clip newClip = createNewClip(filePath); newClip.loop(loopCount); activeClips.add(newClip); newClip.start(); } }
Pros: Minimal boilerplate, straightforward to implement.
Cons: Doesn't scale—every new combination requires a new class.
内容的提问来源于stack exchange,提问作者CSDragon

