RecommendationStrategy interface with collaborative-filtering/content-based implementations, a rule-based placeholder for where ML plugs in later, and one more Strategy pattern rep.
Published September 23, 2026
interface RecommendationStrategy {
List<Product> recommend(User user, int count);
}
class CollaborativeFilteringStrategy implements RecommendationStrategy {
// recommends based on what SIMILAR USERS bought/liked
public List<Product> recommend(User user, int count) {
List<User> similarUsers = findSimilarUsers(user);
return aggregateTopProducts(similarUsers, count);
}
}
class ContentBasedStrategy implements RecommendationStrategy {
// recommends based on ATTRIBUTES of what this specific user has liked before
public List<Product> recommend(User user, int count) {
List<Product> userHistory = user.getPurchaseHistory();
return findSimilarProducts(userHistory, count);
}
}
class RecommendationEngine {
private final RecommendationStrategy strategy; // swappable
List<Product> getRecommendations(User user) { return strategy.recommend(user, 10); }
}
Collaborative filtering ("people similar to you also liked X") and content-based filtering ("you liked Y, and Z shares Y's attributes") are genuinely different algorithms with different data requirements (the former needs a population of other users' behavior; the latter can work from one user's own history plus product metadata alone) — modeling them as interchangeable RecommendationStrategy implementations lets RecommendationEngine stay agnostic to which approach (or combination) is active.
class RuleBasedStrategy implements RecommendationStrategy {
public List<Product> recommend(User user, int count) {
// simple, explainable placeholder: "most popular in categories this user has purchased from"
Set<Category> userCategories = user.getPurchaseHistory().stream().map(Product::getCategory).collect(Collectors.toSet());
return productRepository.findTopSellingIn(userCategories, count);
}
}
This is worth naming explicitly as a deliberate, simple starting implementation — real recommendation systems eventually reach for ML models (embeddings, learned ranking), but the interface stays the same: a future MLRankingStrategy implements RecommendationStrategy slots into RecommendationEngine exactly like RuleBasedStrategy does today, with zero changes to any calling code. This is a genuinely common real-world pattern — ship a simple rule-based version first, behind an abstraction that doesn't need to change when the ML version eventually replaces it.
Q: How would you A/B test two different RecommendationStrategy implementations in production? A: A wrapping strategy (or the engine itself) that routes a percentage of users to each underlying strategy based on a consistent hash of user ID (ensuring the same user always sees the same variant for the test's duration) — the Strategy abstraction makes this a routing decision at the injection point, not a change to either strategy's own logic.
Q: What's a cold-start problem, and how does it affect the choice between these strategies? A: A new user with no purchase history breaks ContentBasedStrategy (nothing to base similarity on) and weakens CollaborativeFilteringStrategy (no signal for 'similar users' to match against) — RuleBasedStrategy's simple 'popular in general' fallback is often specifically valuable for exactly this case, which is a real argument for combining strategies (fall back to rule-based when history is empty) rather than picking one exclusively.
Q: Could RecommendationEngine hold multiple strategies and combine their outputs, rather than just one? A: Yes — a CompositeStrategy implementing the same interface, internally calling several strategies and merging/deduplicating/re-ranking their results, is a natural extension that stays consistent with the same interface, rather than requiring RecommendationEngine to know about combining logic itself.
Q: Why does this design pattern-match to Strategy specifically, rather than, say, Factory? A: The choice being made here is 'which algorithm computes recommendations' (interchangeable behavior behind one interface) — Factory would be relevant if the question were instead 'which concrete RecommendationStrategy object should be CONSTRUCTED given some condition,' a related but distinct concern (see Factory & Abstract Factory) that could reasonably pair with this design (a factory choosing which strategy to instantiate) without being the core pattern itself.