Understanding Architectural Patterns: MVC, MVP, MVI, MVVM, and VIPER

When developing a software application, choosing the right architectural pattern is crucial for maintainability, scalability, and testability. Let's explore the five popular architectural patterns: MVC, MVP, MVI, MVVM, and VIPER, with examples in C#. We will discuss what they are, how they differ, and how to determine the right one for your project.

1. MVC (Model-View-Controller)
 

What is MVC?

MVC is an architectural pattern that separates an application into three main components.

  • Model: Manages data and business logic.
  • View: Displays the data (UI).
  • Controller: Handles the user input and updates the model and view accordingly.
// Model
public class UserModel
{
    public string Username { get; set; }
    public string Password { get; set; }
}

// View
public class UserView
{
    public void DisplayUserDetails(UserModel user)
    {
        Console.WriteLine($"Username: {user.Username}");
    }
}

// Controller
public class UserController
{
    private readonly UserModel _user;
    private readonly UserView _view;

    public UserController(UserModel user, UserView view)
    {
        _user = user;
        _view = view;
    }

    public void SetUserDetails(string username, string password)
    {
        _user.Username = username;
        _user.Password = password;
    }

    public void UpdateView()
    {
        _view.DisplayUserDetails(_user);
    }
}

// Usage
var user = new UserModel();
var view = new UserView();
var controller = new UserController(user, view);

controller.SetUserDetails("john_doe", "password123");
controller.UpdateView();

2. MVP (Model-View-Presenter)
 

What is MVP?

MVP is similar to MVC but with a distinct difference: the presenter handles all the logic to update the view. This makes the view passive, focusing solely on UI representation.

// Model
public class UserModel
{
    public string Username { get; set; }
    public string Password { get; set; }
}

// View Interface
public interface IUserView
{
    void DisplayUserDetails(UserModel user);
}

// View
public class UserView : IUserView
{
    public void DisplayUserDetails(UserModel user)
    {
        Console.WriteLine($"Username: {user.Username}");
    }
}

// Presenter
public class UserPresenter
{
    private readonly UserModel _user;
    private readonly IUserView _view;

    public UserPresenter(UserModel user, IUserView view)
    {
        _user = user;
        _view = view;
    }

    public void SetUserDetails(string username, string password)
    {
        _user.Username = username;
        _user.Password = password;
    }

    public void UpdateView()
    {
        _view.DisplayUserDetails(_user);
    }
}

// Usage
var user = new UserModel();
var view = new UserView();
var presenter = new UserPresenter(user, view);

presenter.SetUserDetails("john_doe", "password123");
presenter.UpdateView();

3. MVI (Model-View-Intent)
 

What is MVI?

MVI is a reactive pattern where.

  • Model: Represents the state.
  • View: Renders the UI based on the state.
  • Intent: Captures the user's intention (actions) and updates the model.
// Model
public class UserModel
{
    public string Username { get; set; }
    public string Password { get; set; }
}

// View Interface
public interface IUserView
{
    void DisplayUserDetails(UserModel user);
    void DisplayError(string message);
}

// Intent
public class UserIntent
{
    public Action<string, string> OnUserDetailsSubmitted;
}

// View
public class UserView : IUserView
{
    public void DisplayUserDetails(UserModel user)
    {
        Console.WriteLine($"Username: {user.Username}");
    }

    public void DisplayError(string message)
    {
        Console.WriteLine($"Error: {message}");
    }
}

// Usage
var user = new UserModel();
var view = new UserView();
var intent = new UserIntent();

intent.OnUserDetailsSubmitted += (username, password) =>
{
    if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
    {
        view.DisplayError("Invalid input");
    }
    else
    {
        user.Username = username;
        user.Password = password;
        view.DisplayUserDetails(user);
    }
};

intent.OnUserDetailsSubmitted("john_doe", "password123");

4. MVVM (Model-View-ViewModel)
 

What is MVVM?

MVVM is widely used in WPF and Xamarin applications. It separates.

  • Model: Data and business logic.
  • View: The UI.
  • ViewModel: An abstraction of the view, exposing properties and commands for data binding.
// Model
public class UserModel
{
    public string Username { get; set; }
    public string Password { get; set; }
}

// ViewModel
public class UserViewModel : INotifyPropertyChanged
{
    private UserModel _user;

    public UserViewModel()
    {
        _user = new UserModel();
    }

    public string Username
    {
        get => _user.Username;
        set
        {
            if (_user.Username != value)
            {
                _user.Username = value;
                OnPropertyChanged(nameof(Username));
            }
        }
    }

    public string Password
    {
        get => _user.Password;
        set
        {
            if (_user.Password != value)
            {
                _user.Password = value;
                OnPropertyChanged(nameof(Password));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

// View (XAML)
/*
<UserControl x:Class="UserView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel>
        <TextBox Text="{Binding Username, UpdateSourceTrigger=PropertyChanged}" />
        <TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" />
    </StackPanel>
</UserControl>
*/

// Usage in code-behind
public partial class UserView : UserControl
{
    public UserView()
    {
        InitializeComponent();
        DataContext = new UserViewModel();
    }
}

5. VIPER (View, Interactor, Presenter, Entity, Router)
 

What is VIPER?

VIPER is an architectural pattern used mainly in iOS development but can be adapted to other platforms. It separates responsibilities into.

  • View: Displays the UI.
  • Interactor: Contains business logic.
  • Presenter: Intermediates between view and interactor.
  • Entity: Models data.
  • Router: Handles navigation.

Due to the complexity and the common use in mobile development, a full example in C# would be quite lengthy. However, here’s a brief outline.

  • View: Interface for UI.
  • Interactor: Interface for business logic.
  • Presenter: Manages the interaction between the view and the interactor.
  • Entity: Data model.
  • Router: Manages navigation.
// View Interface
public interface IUserView
{
    void DisplayUserDetails(UserModel user);
}

// Interactor Interface
public interface IUserInteractor
{
    void FetchUserDetails(Action<UserModel> callback);
}

// Presenter
public class UserPresenter
{
    private readonly IUserView _view;
    private readonly IUserInteractor _interactor;

    public UserPresenter(IUserView view, IUserInteractor interactor)
    {
        _view = view;
        _interactor = interactor;
    }

    public void GetUserDetails()
    {
        _interactor.FetchUserDetails(user => _view.DisplayUserDetails(user));
    }
}

// Router Interface
public interface IUserRouter
{
    void NavigateToUserDetails();
}

// Usage
public class UserView : IUserView
{
    public void DisplayUserDetails(UserModel user)
    {
        Console.WriteLine($"Username: {user.Username}");
    }
}

public class UserInteractor : IUserInteractor
{
    public void FetchUserDetails(Action<UserModel> callback)
    {
        // Fetch user details (e.g., from a database)
        var user = new UserModel { Username = "john_doe", Password = "password123" };
        callback(user);
    }
}

public class UserRouter : IUserRouter
{
    public void NavigateToUserDetails()
    {
        Console.WriteLine("Navigating to user details view...");
    }
}

// Initial setup
var view = new UserView();
var interactor = new UserInteractor();
var presenter = new UserPresenter(view, interactor);
var router = new UserRouter();

presenter.GetUserDetails();
router.NavigateToUserDetails();

Conclusion

Choosing the right architectural pattern depends on your project's requirements, complexity, and the team's familiarity with the pattern. Here are some general guidelines:

  • MVC: Simple applications with a clear separation of concerns.
  • MVP: Applications needing a passive view and testable presentation logic.
  • MVI: Reactive applications where state management is crucial.
  • MVVM: WPF, Xamarin, or other applications with heavy data binding requirements.
  • VIPER: Complex applications requiring high modularity and testability.

Each pattern has its strengths and is best suited for specific scenarios. Understanding these patterns and their appropriate use cases is crucial for building robust and maintainable applications.