Lesson Completion
Back to course

Anti-Patterns and Best Practices: When NOT to Use Patterns

Beginner
12 minutesā˜…4.8Java

1. The Hook (The "Byte-Sized" Intro)

  • In a Nutshell: Anti-patterns = common bad practices. God Object = class does everything (SRP violation), Spaghetti Code = no structure, tangled, Golden Hammer = use one pattern for everything, Premature Optimization = optimize before measuring, Circular Dependencies = A depends on B, B depends on A.
  • Best practices: YAGNI (You Aren't Gonna Need It), KISS (Keep It Simple), measure before optimizing, refactor to patterns (don't force them), balance complexity vs simplicity!

God Object = Swiss Army knife trying to be every tool (does nothing well). Spaghetti Code = tangled earbuds (mess). Golden Hammer = using hammer for every problem (even screws!). Premature Optimization = buying sports car for grocery shopping!


2. Conceptual Clarity (The "Simple" Tier)

šŸ’” The Analogy

  • God Object: Control freak (does everyone's job)
  • Spaghetti Code: Messy room (can't find anything)
  • Golden Hammer: One-trick pony
  • YAGNI: Don't build basement bomb shelter (probably won't need it)

3. Technical Mastery (The "Deep Dive")

java
// =========================================== // ANTI-PATTERNS // =========================================== // āŒ 1. GOD OBJECT class UserManager { // Violates SRP - too many responsibilities! void createUser() { /* ... */ } void deleteUser() { /* ... */ } void validateUser() { /* ... */ } void sendEmail() { /* ... */ } void logActivity() { /* ... */ } void connectToDatabase() { /* ... */ } void generateReport() { /* ... */ } // 100+ methods... } // āœ… FIX: Separate responsibilities class UserService { private UserValidator validator; private UserRepository repository; private EmailService emailService; private Logger logger; } // āŒ 2. SPAGHETTI CODE class OrderProcessor { void process(Order order) { if (order.isValid()) { if (order.hasItems()) { if (order.getTotalreateUser() < 1000) { // Nested if statements... if (order.getCustomer() != null) { // More nesting... } } } } // No structure, hard to follow! } } // āœ… FIX: Extract methods, use early returns class OrderProcessor { void process(Order order) { validateOrder(order); processItems(order); applyDiscounts(order); finalizeOrder(order); } private void validateOrder(Order order) { if (!order.isValid()) { throw new IllegalArgumentException("Invalid order"); } } } // āŒ 3. GOLDEN HAMMER (using Singleton everywhere) class DatabaseConnection { private static DatabaseConnection instance; // Singleton static DatabaseConnection getInstance() { if (instance == null) { instance = new DatabaseConnection(); } return instance; } } class ConfigManager { private static ConfigManager instance; // Another Singleton! // ... } class Logger { private static Logger instance; // Yet another Singleton! // Problem: Overusing Singleton makes testing hard! } // āœ… FIX: Use patterns appropriately // Singleton for truly global resources only (logger, config) // Use Dependency Injection for others // āŒ 4. PREMATURE OPTIMIZATION class DataProcessor { // Overly complex optimization without measuring! int[] complexCache = new int[10000]; Map<String, WeakReference<Object>> pool = new WeakHashMap<>(); void process(List<Data> data) { // Complex caching, pooling, etc. // But... is it even a bottleneck? } } // āœ… FIX: Measure first, optimize later class DataProcessor { void process(List<Data> data) { // Simple, readable code first for (Data d : data) { processItem(d); } // Profile → identify bottleneck → THEN optimize } } // āŒ 5. CIRCULAR DEPENDENCIES class UserService { private OrderService orderService; // Depends on OrderService } class OrderService { private UserService userService; // Depends on UserService! // Circular dependency! } // āœ… FIX: Introduce abstraction or mediator interface UserProvider { User getUser(int id); } class UserService implements UserProvider { // No dependency on OrderService } class OrderService { private UserProvider userProvider; // Depends on abstraction } // =========================================== // BEST PRACTICES // =========================================== // 1. YAGNI (You Aren't Gonna Need It) // āŒ BAD: Over-engineering interface Animal { void eat(); void sleep(); void fly(); // āŒ Not all animals fly! void swim(); // āŒ Not all animals swim! void layEggs(); // āŒ Not all animals lay eggs! } // āœ… GOOD: Start simple interface Animal { void eat(); void sleep(); } // Add specialized interfaces only when needed: interface Flyable { void fly(); } // 2. KISS (Keep It Simple, Stupid) // āŒ BAD: Overuse of patterns class SimpleCalculator { // Using Factory + Strategy + Builder for simple addition! private OperationFactory factory = new OperationFactory(); private OperationStrategy strategy = new AdditionStrategy(); private CalculatorBuilder builder = new CalculatorBuilder(); int add(int a, int b) { Operation op = factory.createOperation(strategy); Calculator calc = builder.withOperation(op).build(); return calc.execute(a, b); } } // āœ… GOOD: Simple solution class SimpleCalculator { int add(int a, int b) { return a + b; // That's it! } } // 3. Refactor TO Patterns (don't force them) // Start simple: class ReportGenerator { void generate(String type) { if ("PDF".equals(type)) { // Generate PDF } else if ("Excel".equals(type)) { // Generate Excel } } } // When complexity grows, refactor to Strategy: interface ReportStrategy { void generate(); } class ReportGenerator { private ReportStrategy strategy; void setStrategy(ReportStrategy strategy) { this.strategy = strategy; } void generate() { strategy.generate(); } }

5. The Comparison & Decision Layer

Anti-PatternFix
God ObjectApply SRP, separate responsibilities
Spaghetti CodeExtract methods, add structure
Golden HammerChoose right tool for job
Premature OptimizationMeasure first, optimize second
Circular DependenciesIntroduce interfaces, mediators

6. The "Interview Corner" (The Edge)

The "Killer" Interview Question: "When should you NOT use design patterns?" Answer:

  1. Simple problems: Don't use Strategy for 2 options
  2. Over-engineering: Patterns add complexity
  3. YAGNI: Don't implement for future "might need"
  4. Performance-critical: Some patterns add overhead
java
// Don't use patterns here: class Greeter { String greet(String name) { return "Hello, " + name; // Simple! No pattern needed } } // DO use patterns here: // - Multiple greeting strategies (Strategy) // - Extensible greetings (Open/Closed) // - Complex greeting rules (Chain of Responsibility)

Pro-Tips:

  1. Pattern selection checklist:
text
āœ… Ask: - Does it solve a real problem? - Is the code complex enough to warrant it? - Will others understand it? - Does it improve maintainability? āŒ Don't use if: - Adds unnecessary complexity - Team doesn't understand pattern - Simple solution exists - "Just because it's cool"
  1. Balance simplicity vs flexibility:
java
// V1: Start simple void process() { // Hardcoded logic } // V2: If requirements change, refactor void process(Strategy strategy) { strategy.execute(); } // Don't jump to V2 until needed!

Topics Covered

Java Fundamentals

Tags

#java#programming#beginner-friendly

Last Updated

2025-02-01