Designing SocialMediaLink in OOP

Beginning of Software Design

Requirement: Design an Actor class which has personal details and social media links.

A note on design:

The current design, with direct properties for each social media link and separate methods for updating/removing each, is more rigid and might lead to more boilerplate code as the number of social media platforms increases.

This design has some potential disadvantages:

  1. Rigidity:
    • If you wanted to add support for another social media platform, you'd have to modify the Actor class, adding a new property and accompanying methods to update and remove the link. This breaks the Open/Closed Principle (OCP) of SOLID, which states that a class should be open for extension but closed for modification.
  2. Duplication:
    • The IsValidLink method is invoked in every UpdateXXXLink method. If the validation logic changes, it will be correctly applied across all methods, but the repetitive structure is a red flag that there might be a more elegant design solution.
  3. No Single Responsibility:
    • The Actor class is currently handling both the data representation of an actor and the management and validation of links. This can be seen as a violation of the Single Responsibility Principle (SRP) of SOLID. Ideally, an Actor shouldn't have to be concerned with the intricacies of URL validation.
  4. Null State Ambiguity:
    • Using null to represent the absence of a link can lead to potential null reference exceptions if not handled properly elsewhere in the code. While setting the value to null does represent the absence of a value, it requires other parts of your application to consistently check for null before using a link.
  5. Lack of History/Tracking:
    • In the current design, there's no way to keep track of changes to an actor's social media links. If link history or auditing is a requirement (either now or in the future), this design would need to be significantly refactored.
  6. Potential for Incomplete Removals:
    • If a developer forgets to call the removal method, old data might remain in the system. In the design where links were contained in a list, you could have just cleared the list or removed specific items, ensuring all links of that type were removed.
  7. Scalability Concerns:
    • As new properties or methods are added, this class will grow, making it harder to maintain. A larger class tends to be more error-prone and harder to debug.

Recommendation:

A more flexible approach would involve encapsulating the behaviour and data of social media links into their own classes (as shown in the next designs). It allows for easier additions of new link types, centralized validation logic, and a clearer separation of concerns.

Find this code in https://github.com/csharp-projects-kenanhancer/csharp-design-patterns/tree/main/example1

public class Actor
{
    public Guid Id { get; private set; }

    // Personal Details
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public string Biography { get; private set; }

    // Social Media Links
    public string Twitter { get; private set; }
    public string Instagram { get; private set; }
    public string Facebook { get; private set; }
    public string OfficialWebsite { get; private set; }

    public Actor(string firstName, string lastName, string biography, 
                 string twitter, string instagram, string facebook, string officialWebsite)
    {
        Id = Guid.NewGuid();
        FirstName = firstName;
        LastName = lastName;
        Biography = biography;
        Twitter = twitter;
        Instagram = instagram;
        Facebook = facebook;
        OfficialWebsite = officialWebsite;
    }

    public void UpdateTwitterLink(string newLink)
    {
        if (!IsValidSocialMediaLink(newLink, "twitter.com"))
            throw new InvalidOperationException("Invalid Twitter link.");

        Twitter = newLink;
    }

    public void UpdateInstagramLink(string newLink)
    {
        if (!IsValidSocialMediaLink(newLink, "instagram.com"))
            throw new InvalidOperationException("Invalid Instagram link.");

        Instagram = newLink;
    }

    public void UpdateFacebookLink(string newLink)
    {
        if (!IsValidSocialMediaLink(newLink, "facebook.com"))
            throw new InvalidOperationException("Invalid Facebook link.");

        Facebook = newLink;
    }

    public void UpdateOfficialWebsite(string newLink)
    {
        if (!IsValidSocialMediaLink(newLink))
            throw new InvalidOperationException("Invalid official website link.");

        OfficialWebsite = newLink;
    }

    public void RemoveTwitterLink()
    {
        Twitter = null;  // Assuming null indicates no link.
    }

    public void RemoveInstagramLink()
    {
        Instagram = null;
    }

    public void RemoveFacebookLink()
    {
        Facebook = null;
    }

    public void RemoveOfficialWebsite()
    {
        OfficialWebsite = null;
    }

    private bool IsValidSocialMediaLink(string link, string expectedDomain = null)
    {
        if (string.IsNullOrWhiteSpace(link))
            return false;

        if (!Uri.TryCreate(link, UriKind.Absolute, out Uri uri))
            return false;

        // If an expected domain is provided, check if the link belongs to that domain.
        if (expectedDomain != null && !uri.Host.EndsWith(expectedDomain, StringComparison.OrdinalIgnoreCase))
            return false;

        return true;
    }
}
var actor = new Actor("John", "Doe", "John Doe is an acclaimed actor...", "https://twitter.com/johndoe",
    "https://instagram.com/johndoe",
    "https://facebook.com/johndoe", "https://www.johndoe.com");

// Get a specific social media link
Console.WriteLine($"Original Twitter: {actor.Twitter}");
Console.WriteLine($"Original Instagram: {actor.Instagram}");
Console.WriteLine($"Original Facebook: {actor.Facebook}");
Console.WriteLine($"Original OfficialWebsite: {actor.OfficialWebsite}");

// Update social media links for the actor
// V1
actor.UpdateTwitterLink("https://twitter.com/new-johndoe");
actor.UpdateInstagramLink("https://instagram.com/new-johndoe");
actor.UpdateFacebookLink("https://facebook.com/new-johndoe");
actor.UpdateOfficialWebsite("https://www.new-johndoe.com");

// Get a specific social media link
Console.WriteLine($"Updated Twitter: {actor.Twitter}");
Console.WriteLine($"Updated Instagram: {actor.Instagram}");
Console.WriteLine($"Updated Facebook: {actor.Facebook}");
Console.WriteLine($"Updated OfficialWebsite: {actor.OfficialWebsite}");

// Remove a specific social media link
// V1
actor.RemoveTwitterLink();
actor.RemoveInstagramLink();
actor.RemoveFacebookLink();
actor.RemoveOfficialWebsite();
$ dotnet run

Original Twitter: https://twitter.com/johndoe
Original Instagram: https://instagram.com/johndoe
Original Facebook: https://facebook.com/johndoe
Original OfficialWebsite: https://www.johndoe.com
Updated Twitter: https://twitter.com/new-johndoe
Updated Instagram: https://instagram.com/new-johndoe
Updated Facebook: https://facebook.com/new-johndoe
Updated OfficialWebsite: https://www.new-johndoe.com

Encapsulating Social Media Links in a class

Change on design:

To maintain a clear separation of concern, we have relocated the properties for Twitter, Instagram, Facebook and OfficialWebsite, along with their respective update/remove methods, from the Actor class to the SocialMediaLinks class. This design choice allows the Actor class to concentrate solely on encapsulating actor-related attributes, while the SocialMediaLinks class assumes the full responsibility of managing social media links.

Find this code in https://github.com/csharp-projects-kenanhancer/csharp-design-patterns/tree/main/example2

public class SocialMediaLinks
{
    public string? Twitter { get; private set; }
    public string? Instagram { get; private set; }
    public string? Facebook { get; private set; }
    public string? OfficialWebsite { get; private set; }

    public SocialMediaLinks(string? twitter, string? instagram, string? facebook, string? officialWebsite)
    {
        Twitter = twitter;
        Instagram = instagram;
        Facebook = facebook;
        OfficialWebsite = officialWebsite;
    }

    public void UpdateTwitterLink(string newLink)
    {
        if (!IsValidSocialMediaLink(newLink, "twitter.com"))
            throw new InvalidOperationException("Invalid Twitter link.");

        Twitter = newLink;
    }

    public void UpdateInstagramLink(string newLink)
    {
        if (!IsValidSocialMediaLink(newLink, "instagram.com"))
            throw new InvalidOperationException("Invalid Instagram link.");

        Instagram = newLink;
    }

    public void UpdateFacebookLink(string newLink)
    {
        if (!IsValidSocialMediaLink(newLink, "facebook.com"))
            throw new InvalidOperationException("Invalid Facebook link.");

        Facebook = newLink;
    }

    public void UpdateOfficialWebsite(string newLink)
    {
        if (!IsValidSocialMediaLink(newLink))
            throw new InvalidOperationException("Invalid official website link.");

        OfficialWebsite = newLink;
    }

    public void RemoveTwitterLink()
    {
        Twitter = null;
    }

    public void RemoveInstagramLink()
    {
        Instagram = null;
    }

    public void RemoveFacebookLink()
    {
        Facebook = null;
    }

    public void RemoveOfficialWebsite()
    {
        OfficialWebsite = null;
    }

    private bool IsValidSocialMediaLink(string link, string? expectedDomain = null)
    {
        if (String.IsNullOrWhiteSpace(link))
            return false;

        if (!Uri.TryCreate(link, UriKind.Absolute, out Uri? uri))
            return false;

        // If an expected domain is provided, check if the link belongs to that domain.
        if (expectedDomain != null && !uri.Host.EndsWith(expectedDomain, StringComparison.OrdinalIgnoreCase))
            return false;

        return true;
    }
}
public class Actor
{
    public Guid Id { get; private set; }

    // Personal Details
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public string Biography { get; private set; }

    // Social Media Links
    public SocialMediaLinks SocialMediaLinks { get; private set; }

    public Actor(string firstName, string lastName, string biography, SocialMediaLinks socialMediaLinks)
    {
        Id = Guid.NewGuid();
        FirstName = firstName;
        LastName = lastName;
        Biography = biography;
        SocialMediaLinks = socialMediaLinks;
    }
}
var socialMediaLinks = new SocialMediaLinks("https://twitter.com/johndoe",
    "https://instagram.com/johndoe",
    "https://facebook.com/johndoe", "https://www.johndoe.com");

var actor = new Actor("John", "Doe", "John Doe is an acclaimed actor...", socialMediaLinks);

// Get a specific social media link
Console.WriteLine($"Original Twitter: {actor.SocialMediaLinks.Twitter}");
Console.WriteLine($"Original Instagram: {actor.SocialMediaLinks.Instagram}");
Console.WriteLine($"Original Facebook: {actor.SocialMediaLinks.Facebook}");
Console.WriteLine($"Original OfficialWebsite: {actor.SocialMediaLinks.OfficialWebsite}");

// Update social media links for the actor
// V1
// actor.UpdateTwitterLink("https://twitter.com/new-johndoe");
// actor.UpdateInstagramLink("https://instagram.com/new-johndoe");
// actor.UpdateFacebookLink("https://facebook.com/new-johndoe");
// actor.UpdateOfficialWebsite("https://www.new-johndoe.com");

// V2   
actor.SocialMediaLinks.UpdateTwitterLink("https://twitter.com/new-johndoe");
actor.SocialMediaLinks.UpdateInstagramLink("https://instagram.com/new-johndoe");
actor.SocialMediaLinks.UpdateFacebookLink("https://facebook.com/new-johndoe");
actor.SocialMediaLinks.UpdateOfficialWebsite("https://www.new-johndoe.com");

// Get a specific social media link
Console.WriteLine($"Updated Twitter: {actor.SocialMediaLinks.Twitter}");
Console.WriteLine($"Updated Instagram: {actor.SocialMediaLinks.Instagram}");
Console.WriteLine($"Updated Facebook: {actor.SocialMediaLinks.Facebook}");
Console.WriteLine($"Updated OfficialWebsite: {actor.SocialMediaLinks.OfficialWebsite}");

// Remove a specific social media link
// V1
// actor.RemoveTwitterLink();
// actor.RemoveInstagramLink();
// actor.RemoveFacebookLink();
// actor.RemoveOfficialWebsite();

// V2
actor.SocialMediaLinks.RemoveTwitterLink();
actor.SocialMediaLinks.RemoveInstagramLink();
actor.SocialMediaLinks.RemoveFacebookLink();
actor.SocialMediaLinks.RemoveOfficialWebsite();
$ dotnet run

Original Twitter: https://twitter.com/johndoe
Original Instagram: https://instagram.com/johndoe
Original Facebook: https://facebook.com/johndoe
Original OfficialWebsite: https://www.johndoe.com
Updated Twitter: https://twitter.com/new-johndoe
Updated Instagram: https://instagram.com/new-johndoe
Updated Facebook: https://facebook.com/new-johndoe
Updated OfficialWebsite: https://www.new-johndoe.com

a note on design:

I extracted the social media links into their own class (SocialMediaLinks) and are associating that with the Actor class. This offers a good level of separation and encapsulation.

This design has some potential disadvantages:

1. Excessive Granularity in Methods: There are individual methods for updating and removing each social media link. This increases the number of methods and makes the interface more complex. A more general method that accepts an enum or string parameter indicating the platform might simplify the design.
2. Exposure of Internal State: The SocialMediaLinks property is public in the Actor class, which means any external component can access and potentially misuse the SocialMediaLinks instance, even if they can't directly modify its properties.
3. Lack of Flexibility in Social Media Platforms: The design is rigid with respect to the set of social media platforms. If a new social media platform becomes popular, the classes would need to be modified to accommodate it.

Refactoring above design: consolidating remove/update methods in a single method

Change on design:

– This approach consolidates methods and uses a single method for updating and another for removing links, depending on the type of social media provided.
– Validation is added in Constructor of SocialMediaLinks class.

This design has some potential disadvantages:

  1. Switch Overhead: Relying on switch statements can increase the cognitive overhead as the number of cases grow. It can become challenging to manage.
  2. Potential Violation of Open-Closed Principle: Every time a new social media type is added, you will need to modify the existing UpdateLink and RemoveLink methods. The Open-Closed Principle (a SOLID principle) states that entities should be open for extension but closed for modification.
  3. Harder to Track Individual Logic: If each type has unique logic, it can become challenging to manage within the consolidated method.

Find this code in https://github.com/csharp-projects-kenanhancer/csharp-design-patterns/tree/main/example3

public enum SocialMediaType
{
    Twitter,
    Instagram,
    Facebook,
    OfficialWebsite,
    YouTube,
    LinkedIn,
    GitHub,
    Twitch,
    TikTok,
    Pinterest,
    Snapchat,
    Reddit,
    Tumblr,
    WhatsApp,
    Telegram,
    Discord,
    Skype,
    Viber,
    WeChat,
    Line,
    Quora,
    Medium,
    Flickr,
    Meetup,
    Slack,
    StackOverflow,
    Yelp,
    Foursquare,
    Spotify,
    Steam,
    Vimeo,
    WordPress,
    Blogger,
    Blogspot
}
public class SocialMediaLinks
{
    public string? Twitter { get; private set; }
    public string? Instagram { get; private set; }
    public string? Facebook { get; private set; }
    public string? OfficialWebsite { get; private set; }

    public SocialMediaLinks(string? twitter, string? instagram, string? facebook, string? officialWebsite)
    {
        if (twitter != null && !IsValidSocialMediaLink(twitter, SocialMediaType.Twitter))
            throw new ArgumentException("Invalid Twitter link.", nameof(twitter));
        if (instagram != null && !IsValidSocialMediaLink(instagram, SocialMediaType.Instagram))
            throw new ArgumentException("Invalid Instagram link.", nameof(instagram));
        if (facebook != null && !IsValidSocialMediaLink(facebook, SocialMediaType.Facebook))
            throw new ArgumentException("Invalid Facebook link.", nameof(facebook));
        if (officialWebsite != null && !IsValidSocialMediaLink(officialWebsite, SocialMediaType.OfficialWebsite))
            throw new ArgumentException("Invalid OfficialWebsite link.", nameof(officialWebsite));

        Twitter = twitter;
        Instagram = instagram;
        Facebook = facebook;
        OfficialWebsite = officialWebsite;
    }

    public void UpdateLink(SocialMediaType type, string newLink)
    {
        if (!IsValidSocialMediaLink(newLink, type))
            throw new InvalidOperationException($"Invalid {type} link.");

        switch (type)
        {
            case SocialMediaType.Twitter:
                break;
            case SocialMediaType.Instagram:
                break;
            case SocialMediaType.Facebook:
                break;
            case SocialMediaType.OfficialWebsite:
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(type), type, $"Unsupported social media type: {type}");
        }
    }

    public void RemoveLink(SocialMediaType type)
    {
        switch (type)
        {
            case SocialMediaType.Twitter:
                Twitter = null;
                break;
            case SocialMediaType.Instagram:
                Instagram = null;
                break;
            case SocialMediaType.Facebook:
                Facebook = null;
                break;
            case SocialMediaType.OfficialWebsite:
                OfficialWebsite = null;
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(type), type, $"Unsupported social media type: {type}");
        }
    }

    private bool IsValidSocialMediaLink(string link, SocialMediaType type)
    {
        if (string.IsNullOrWhiteSpace(link) || !Uri.TryCreate(link, UriKind.Absolute, out Uri? uri))
            return false;

        // If an expected domain is provided, check if the link belongs to that domain.
        var expectedDomain = GetExpectedDomain(type);

        if (expectedDomain != null && !uri.Host.EndsWith(expectedDomain, StringComparison.OrdinalIgnoreCase))
            return false;

        return true;
    }

    private string? GetExpectedDomain(SocialMediaType type)
    {
        return type switch
        {
            SocialMediaType.Twitter => "twitter.com",
            SocialMediaType.Instagram => "instagram.com",
            SocialMediaType.Facebook => "facebook.com",
            SocialMediaType.OfficialWebsite => null, // No specific domain for the official website
            _ => throw new ArgumentOutOfRangeException(nameof(type), $"Unsupported social media tye: {type}")
        };
    }
}
public class Actor
{
    public Guid Id { get; private set; }

    // Personal Details
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public string Biography { get; private set; }

    // Social Media Links
    public SocialMediaLinks SocialMediaLinks { get; private set; }

    public Actor(string firstName, string lastName, string biography, SocialMediaLinks socialMediaLinks)
    {
        Id = Guid.NewGuid();
        FirstName = firstName ?? throw new ArgumentNullException(nameof(firstName));
        LastName = lastName ?? throw new ArgumentNullException(nameof(lastName));
        Biography = biography ?? throw new ArgumentNullException(nameof(biography));
        SocialMediaLinks = socialMediaLinks ?? throw new ArgumentNullException(nameof(socialMediaLinks));
    }
}
var socialMediaLinks = new SocialMediaLinks("https://twitter.com/johndoe",
    "https://instagram.com/johndoe",
    "https://facebook.com/johndoe", "https://www.johndoe.com");

var actor = new Actor("John", "Doe", "John Doe is an acclaimed actor...", socialMediaLinks);

// Get a specific social media link
Console.WriteLine($"Original Twitter: {actor.SocialMediaLinks.Twitter}");
Console.WriteLine($"Original Instagram: {actor.SocialMediaLinks.Instagram}");
Console.WriteLine($"Original Facebook: {actor.SocialMediaLinks.Facebook}");
Console.WriteLine($"Original OfficialWebsite: {actor.SocialMediaLinks.OfficialWebsite}");

// Update social media links for the actor
// V1
// actor.UpdateTwitterLink("https://twitter.com/new-johndoe");
// actor.UpdateInstagramLink("https://instagram.com/new-johndoe");
// actor.UpdateFacebookLink("https://facebook.com/new-johndoe");
// actor.UpdateOfficialWebsite("https://www.new-johndoe.com");

// V2
// actor.SocialMediaLinks.UpdateTwitterLink("https://twitter.com/new-johndoe");
// actor.SocialMediaLinks.UpdateInstagramLink("https://instagram.com/new-johndoe");
// actor.SocialMediaLinks.UpdateFacebookLink("https://facebook.com/new-johndoe");
// actor.SocialMediaLinks.UpdateOfficialWebsite("https://www.new-johndoe.com");

// V3
actor.SocialMediaLinks.UpdateLink(SocialMediaType.Twitter, "https://twitter.com/new-johndoe");
actor.SocialMediaLinks.UpdateLink(SocialMediaType.Instagram, "https://instagram.com/new-johndoe");
actor.SocialMediaLinks.UpdateLink(SocialMediaType.Facebook, "https://facebook.com/new-johndoe");
actor.SocialMediaLinks.UpdateLink(SocialMediaType.OfficialWebsite, "https://www.new-johndoe.com");

// Get a specific social media link
Console.WriteLine($"Updated Twitter: {actor.SocialMediaLinks.Twitter}");
Console.WriteLine($"Updated Instagram: {actor.SocialMediaLinks.Instagram}");
Console.WriteLine($"Updated Facebook: {actor.SocialMediaLinks.Facebook}");
Console.WriteLine($"Updated OfficialWebsite: {actor.SocialMediaLinks.OfficialWebsite}");

// Remove a specific social media link
// V1
// actor.RemoveTwitterLink();
// actor.RemoveInstagramLink();
// actor.RemoveFacebookLink();
// actor.RemoveOfficialWebsite();

// V2
// actor.SocialMediaLinks.RemoveTwitterLink();
// actor.SocialMediaLinks.RemoveInstagramLink();
// actor.SocialMediaLinks.RemoveFacebookLink();
// actor.SocialMediaLinks.RemoveOfficialWebsite();

// V3
actor.SocialMediaLinks.RemoveLink(SocialMediaType.Twitter);
actor.SocialMediaLinks.RemoveLink(SocialMediaType.Instagram);
actor.SocialMediaLinks.RemoveLink(SocialMediaType.Facebook);
actor.SocialMediaLinks.RemoveLink(SocialMediaType.OfficialWebsite);
$ dotnet run

Original Twitter: https://twitter.com/johndoe
Original Instagram: https://instagram.com/johndoe
Original Facebook: https://facebook.com/johndoe
Original OfficialWebsite: https://www.johndoe.com
Updated Twitter: https://twitter.com/johndoe
Updated Instagram: https://instagram.com/johndoe
Updated Facebook: https://facebook.com/johndoe
Updated OfficialWebsite: https://www.johndoe.com

Refactoring more for immutability

Change on design:

– To ensure consistency in validation and link updates, the SocialMediaLinks constructor utilizes the UpdateLink method, thereby minimazing potential errors.
– The UpdateLink and RemoveLink methods produce modified clones of the SocialMediaLinks instance, which can then be integrated using SetSocialMediaLinks method in the Actor class.

Find this code in https://github.com/csharp-projects-kenanhancer/csharp-design-patterns/tree/main/example4

public class SocialMediaLinks
{
    public string? Twitter { get; private set; }
    public string? Instagram { get; private set; }
    public string? Facebook { get; private set; }
    public string? OfficialWebsite { get; private set; }

    public SocialMediaLinks(string? twitter, string? instagram, string? facebook, string? officialWebsite)
    {
        if (twitter != null)
            ValidateSocialMediaLink(SocialMediaType.Twitter, twitter);
        if (instagram != null)
            ValidateSocialMediaLink(SocialMediaType.Instagram, instagram);
        if (facebook != null)
            ValidateSocialMediaLink(SocialMediaType.Facebook, facebook);
        if (officialWebsite != null)
            ValidateSocialMediaLink(SocialMediaType.OfficialWebsite, officialWebsite);

        Twitter = twitter;
        Instagram = instagram;
        Facebook = facebook;
        OfficialWebsite = officialWebsite;
    }

    public SocialMediaLinks UpdateLink(SocialMediaType type, string newLink)
    {
        ValidateSocialMediaLink(type, newLink);

        var updated = Clone();

        switch (type)
        {
            case SocialMediaType.Twitter:
                break;
            case SocialMediaType.Instagram:
                break;
            case SocialMediaType.Facebook:
                break;
            case SocialMediaType.OfficialWebsite:
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(type), type, $"Unsupported social media type: {type}");
        }

        return updated;
    }

    public SocialMediaLinks RemoveLink(SocialMediaType type)
    {
        var updated = Clone();

        switch (type)
        {
            case SocialMediaType.Twitter:
                updated.Twitter = null;
                break;
            case SocialMediaType.Instagram:
                updated.Instagram = null;
                break;
            case SocialMediaType.Facebook:
                updated.Facebook = null;
                break;
            case SocialMediaType.OfficialWebsite:
                updated.OfficialWebsite = null;
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(type), type, $"Unsupported social media type: {type}");
        }

        return updated;
    }

    private void ValidateSocialMediaLink(SocialMediaType type, string link)
    {
        if (!IsValidSocialMediaLink(link, type))
            throw new InvalidOperationException($"Invalid {type} link.");
    }

    private bool IsValidSocialMediaLink(string link, SocialMediaType type)
    {
        if (string.IsNullOrWhiteSpace(link) || !Uri.TryCreate(link, UriKind.Absolute, out Uri? uri))
            return false;

        // If an expected domain is provided, check if the link belongs to that domain.
        var expectedDomain = GetExpectedDomain(type);

        if (expectedDomain != null && !uri.Host.EndsWith(expectedDomain, StringComparison.OrdinalIgnoreCase))
            return false;

        return true;
    }

    private string? GetExpectedDomain(SocialMediaType type)
    {
        return type switch
        {
            SocialMediaType.Twitter => "twitter.com",
            SocialMediaType.Instagram => "instagram.com",
            SocialMediaType.Facebook => "facebook.com",
            SocialMediaType.OfficialWebsite => null, // No specific domain for the official website
            _ => throw new ArgumentOutOfRangeException(nameof(type), $"Unsupported social media tye: {type}")
        };
    }

    private SocialMediaLinks Clone() => new SocialMediaLinks(Twitter, Instagram, Facebook, OfficialWebsite);
}
public class Actor
{
    public Guid Id { get; private set; }

    // Personal Details
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public string Biography { get; private set; }

    // Social Media Links
    public SocialMediaLinks SocialMediaLinks { get; private set; }

    public Actor(string firstName, string lastName, string biography, SocialMediaLinks socialMediaLinks)
    {
        Id = Guid.NewGuid();
        // FirstName = firstName is not null and not "" ? firstName : throw new ArgumentNullException(nameof(firstName));
        FirstName = !string.IsNullOrWhiteSpace(firstName) ? firstName : throw new ArgumentNullException(nameof(firstName));
        LastName = !string.IsNullOrWhiteSpace(lastName) ? lastName : throw new ArgumentNullException(nameof(lastName));
        Biography = !string.IsNullOrWhiteSpace(biography) ? biography : throw new ArgumentNullException(nameof(biography));
        SetSocialMediaLinks(socialMediaLinks);
    }

    public void SetSocialMediaLinks(SocialMediaLinks newSocialMediaLinks)
    {
        SocialMediaLinks = newSocialMediaLinks ?? throw new ArgumentNullException(nameof(newSocialMediaLinks));
    }
}
var socialMediaLinks = new SocialMediaLinks("https://twitter.com/johndoe",
    "https://instagram.com/johndoe",
    "https://facebook.com/johndoe", "https://www.johndoe.com");

var actor = new Actor("John", "Doe", "John Doe is an acclaimed actor...", socialMediaLinks);

// Get a specific social media link
Console.WriteLine($"Original Twitter: {actor.SocialMediaLinks.Twitter}");
Console.WriteLine($"Original Instagram: {actor.SocialMediaLinks.Instagram}");
Console.WriteLine($"Original Facebook: {actor.SocialMediaLinks.Facebook}");
Console.WriteLine($"Original OfficialWebsite: {actor.SocialMediaLinks.OfficialWebsite}");

// Update social media links for the actor 
// V1
// actor.UpdateTwitterLink("https://twitter.com/new-johndoe");
// actor.UpdateInstagramLink("https://instagram.com/new-johndoe");
// actor.UpdateFacebookLink("https://facebook.com/new-johndoe");
// actor.UpdateOfficialWebsite("https://www.new-johndoe.com");

// V2
// actor.SocialMediaLinks.UpdateTwitterLink("https://twitter.com/new-johndoe");
// actor.SocialMediaLinks.UpdateInstagramLink("https://instagram.com/new-johndoe");
// actor.SocialMediaLinks.UpdateFacebookLink("https://facebook.com/new-johndoe");
// actor.SocialMediaLinks.UpdateOfficialWebsite("https://www.new-johndoe.com");

// V3
// actor.SocialMediaLinks.UpdateLink(SocialMediaType.Twitter, "https://twitter.com/new-johndoe");
// actor.SocialMediaLinks.UpdateLink(SocialMediaType.Instagram, "https://instagram.com/new-johndoe");
// actor.SocialMediaLinks.UpdateLink(SocialMediaType.Facebook, "https://facebook.com/new-johndoe");
// actor.SocialMediaLinks.UpdateLink(SocialMediaType.OfficialWebsite, "https://www.new-johndoe.com");

// V4 - Immutable
actor.SetSocialMediaLinks(
    actor.SocialMediaLinks.UpdateLink(SocialMediaType.Twitter, "https://twitter.com/new-johndoe"));
actor.SetSocialMediaLinks(
    actor.SocialMediaLinks.UpdateLink(SocialMediaType.Instagram, "https://instagram.com/new-johndoe"));
actor.SetSocialMediaLinks(
    actor.SocialMediaLinks.UpdateLink(SocialMediaType.Facebook, "https://facebook.com/new-johndoe"));
actor.SetSocialMediaLinks(
    actor.SocialMediaLinks.UpdateLink(SocialMediaType.OfficialWebsite, "https://www.new-johndoe.com"));

// Get a specific social media link
Console.WriteLine($"Updated Twitter: {actor.SocialMediaLinks.Twitter}");
Console.WriteLine($"Updated Instagram: {actor.SocialMediaLinks.Instagram}");
Console.WriteLine($"Updated Facebook: {actor.SocialMediaLinks.Facebook}");
Console.WriteLine($"Updated OfficialWebsite: {actor.SocialMediaLinks.OfficialWebsite}");

// Remove a specific social media link
// V1
// actor.RemoveTwitterLink();
// actor.RemoveInstagramLink();
// actor.RemoveFacebookLink();
// actor.RemoveOfficialWebsite();

// V2
// actor.SocialMediaLinks.RemoveTwitterLink();
// actor.SocialMediaLinks.RemoveInstagramLink();
// actor.SocialMediaLinks.RemoveFacebookLink();
// actor.SocialMediaLinks.RemoveOfficialWebsite();

// V3
// actor.SocialMediaLinks.RemoveLink(SocialMediaType.Twitter);
// actor.SocialMediaLinks.RemoveLink(SocialMediaType.Instagram);
// actor.SocialMediaLinks.RemoveLink(SocialMediaType.Facebook);
// actor.SocialMediaLinks.RemoveLink(SocialMediaType.OfficialWebsite);

// V4 - Immutable
actor.SetSocialMediaLinks(actor.SocialMediaLinks.RemoveLink(SocialMediaType.Twitter));
actor.SetSocialMediaLinks(actor.SocialMediaLinks.RemoveLink(SocialMediaType.Instagram));
actor.SetSocialMediaLinks(actor.SocialMediaLinks.RemoveLink(SocialMediaType.Facebook));
actor.SetSocialMediaLinks(actor.SocialMediaLinks.RemoveLink(SocialMediaType.OfficialWebsite));
$ dotnet run

Original Twitter: https://twitter.com/johndoe
Original Instagram: https://instagram.com/johndoe
Original Facebook: https://facebook.com/johndoe
Original OfficialWebsite: https://www.johndoe.com
Updated Twitter: https://twitter.com/johndoe
Updated Instagram: https://instagram.com/johndoe
Updated Facebook: https://facebook.com/johndoe
Updated OfficialWebsite: https://www.johndoe.com

Encapsulating in SocialMediaLink Concrete class

Change on design:

SocialMediaLink` acts as a unified representation for all social media types. With each added business rule or validation for a specific social media link, its complexity grows since it must house the rules and validations for each link in a singular class.

Find this code in https://github.com/csharp-projects-kenanhancer/csharp-design-patterns/tree/main/Example5

public class SocialMediaLink
{
    public SocialMediaType Type { get; }
    public string Link { get; private set; }

    public SocialMediaLink(SocialMediaType type, string link)
    {
        Type = type;
        UpdateLink(link);
    }

    public void UpdateLink(string link)
    {
        Link = IsValidSocialMediaLink(Type, link)
            ? link
            : throw new ArgumentException($"Invalid {Type} link.", nameof(link));
    }

    private bool IsValidSocialMediaLink(SocialMediaType type, string link)
    {
        if (String.IsNullOrWhiteSpace(link))
            return false;

        if (!Uri.TryCreate(link, UriKind.Absolute, out Uri? uri))
            return false;

        string expectedDomain = GetExpectedDomain(type);

        // If an expected domain is provided, check if the link belongs to that domain.
        if (!String.IsNullOrWhiteSpace(expectedDomain) &&
            !uri.Host.EndsWith(expectedDomain, StringComparison.OrdinalIgnoreCase))
            return false;

        return true;
    }

    private string GetExpectedDomain(SocialMediaType type)
    {
        return type switch
        {
            SocialMediaType.Twitter => "twitter.com",
            SocialMediaType.Instagram => "instagram.com",
            SocialMediaType.Facebook => "facebook.com",
            SocialMediaType.OfficialWebsite => "",
            _ => throw new ArgumentOutOfRangeException(nameof(type), type, $"Unsupported social media type: {type}")
        };
    }
}
public class SocialMediaLinks
{
    private readonly List<SocialMediaLink> _links = new List<SocialMediaLink>();

    public IReadOnlyCollection<SocialMediaLink> Links => _links.AsReadOnly();

    public void AddLink(SocialMediaType type, string link)
    {
        if (_links.Any(l => l.Type == type))
            throw new InvalidOperationException($"Link of type {type} already exists. Use UpdateLink to modify.");

        _links.Add(new SocialMediaLink(type, link));
    }

    public void UpdateLink(SocialMediaType type, string link)
    {
        var existingLink = _links.FirstOrDefault(l => l.Type == type);
        if (existingLink == null)
            throw new InvalidOperationException($"Link of type {type} does not exist. Use AddLink to add a new link.");

        existingLink.UpdateLink(link);
    }

    public void RemoveLink(SocialMediaType type)
    {
        var link = _links.FirstOrDefault(l => l.Type == type);
        if (link == null)
            throw new InvalidOperationException($"Link of type {type} does not exist.");

        _links.Remove(link);
    }

    public string? GetLink(SocialMediaType type)
    {
        var link = _links.FirstOrDefault(l => l.Type == type);
        return link?.Link;
    }
}
public class Actor
{
    public Guid Id { get; private set; }

    // Personal Details
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public string Biography { get; private set; }

    // Social Media Links
    public SocialMediaLinks SocialMediaLinks { get; private set; }

    public Actor(string firstName, string lastName, string biography, SocialMediaLinks socialMediaLinks)
    {
        Id = Guid.NewGuid();
        FirstName = String.IsNullOrWhiteSpace(firstName)
            ? firstName
            : throw new ArgumentNullException(nameof(firstName));
        LastName = !String.IsNullOrWhiteSpace(lastName) ? lastName : throw new ArgumentNullException(nameof(lastName));
        Biography = !String.IsNullOrWhiteSpace(biography)
            ? biography
            : throw new ArgumentNullException(nameof(biography));
        SocialMediaLinks = socialMediaLinks ?? throw new ArgumentNullException(nameof(socialMediaLinks));
    }
}
var socialMediaLinks = new SocialMediaLinks();
socialMediaLinks.AddLink(SocialMediaType.Twitter, "https://twitter.com/johndoe");
socialMediaLinks.AddLink(SocialMediaType.Instagram, "https://instagram.com/johndoe");
socialMediaLinks.AddLink(SocialMediaType.Facebook, "https://facebook.com/johndoe");
socialMediaLinks.AddLink(SocialMediaType.OfficialWebsite, "https://www.johndoe.com");

var actor = new Actor("John", "Doe", "John Doe is an acclaimed actor...", socialMediaLinks);

// Get a specific social media link
Console.WriteLine($"Original Twitter: {actor.SocialMediaLinks.GetLink(SocialMediaType.Twitter)}");
Console.WriteLine($"Original Instagram: {actor.SocialMediaLinks.GetLink(SocialMediaType.Instagram)}");
Console.WriteLine($"Original Facebook: {actor.SocialMediaLinks.GetLink(SocialMediaType.Facebook)}");
Console.WriteLine($"Original OfficialWebsite: {actor.SocialMediaLinks.GetLink(SocialMediaType.OfficialWebsite)}");

// Update social media links for the actor 
// V1
// actor.UpdateTwitterLink("https://twitter.com/new-johndoe");
// actor.UpdateInstagramLink("https://instagram.com/new-johndoe");
// actor.UpdateFacebookLink("https://facebook.com/new-johndoe");
// actor.UpdateOfficialWebsite("https://www.new-johndoe.com");

// V2
// actor.SocialMediaLinks.UpdateTwitterLink("https://twitter.com/new-johndoe");
// actor.SocialMediaLinks.UpdateInstagramLink("https://instagram.com/new-johndoe");
// actor.SocialMediaLinks.UpdateFacebookLink("https://facebook.com/new-johndoe");
// actor.SocialMediaLinks.UpdateOfficialWebsite("https://www.new-johndoe.com");

// V3
// actor.SocialMediaLinks.UpdateLink(SocialMediaType.Twitter, "https://twitter.com/new-johndoe");
// actor.SocialMediaLinks.UpdateLink(SocialMediaType.Instagram, "https://instagram.com/new-johndoe");
// actor.SocialMediaLinks.UpdateLink(SocialMediaType.Facebook, "https://facebook.com/new-johndoe");
// actor.SocialMediaLinks.UpdateLink(SocialMediaType.OfficialWebsite, "https://www.new-johndoe.com");

// V4 - Immutable
// actor.SetSocialMediaLinks(
//     actor.SocialMediaLinks.UpdateLink(SocialMediaType.Twitter, "https://twitter.com/new-johndoe"));
// actor.SetSocialMediaLinks(
//     actor.SocialMediaLinks.UpdateLink(SocialMediaType.Instagram, "https://instagram.com/new-johndoe"));
// actor.SetSocialMediaLinks(
//     actor.SocialMediaLinks.UpdateLink(SocialMediaType.Facebook, "https://facebook.com/new-johndoe"));
// actor.SetSocialMediaLinks(
//     actor.SocialMediaLinks.UpdateLink(SocialMediaType.OfficialWebsite, "https://www.new-johndoe.com"));

// V5
actor.SocialMediaLinks.UpdateLink(SocialMediaType.Twitter, "https://twitter.com/new-johndoe");
actor.SocialMediaLinks.UpdateLink(SocialMediaType.Instagram, "https://instagram.com/new-johndoe");
actor.SocialMediaLinks.UpdateLink(SocialMediaType.Facebook, "https://facebook.com/new-johndoe");
actor.SocialMediaLinks.UpdateLink(SocialMediaType.OfficialWebsite, "https://www.new-johndoe.com");

// Get a specific social media link
Console.WriteLine($"Updated Twitter: {actor.SocialMediaLinks.GetLink(SocialMediaType.Twitter)}");
Console.WriteLine($"Updated Instagram: {actor.SocialMediaLinks.GetLink(SocialMediaType.Instagram)}");
Console.WriteLine($"Updated Facebook: {actor.SocialMediaLinks.GetLink(SocialMediaType.Facebook)}");
Console.WriteLine($"Updated OfficialWebsite: {actor.SocialMediaLinks.GetLink(SocialMediaType.OfficialWebsite)}");

// Remove a specific social media link
// V1
// actor.RemoveTwitterLink();
// actor.RemoveInstagramLink();
// actor.RemoveFacebookLink();
// actor.RemoveOfficialWebsite();

// V2
// actor.SocialMediaLinks.RemoveTwitterLink();
// actor.SocialMediaLinks.RemoveInstagramLink();
// actor.SocialMediaLinks.RemoveFacebookLink();
// actor.SocialMediaLinks.RemoveOfficialWebsite();

// V3
// actor.SocialMediaLinks.RemoveLink(SocialMediaType.Twitter);
// actor.SocialMediaLinks.RemoveLink(SocialMediaType.Instagram);
// actor.SocialMediaLinks.RemoveLink(SocialMediaType.Facebook);
// actor.SocialMediaLinks.RemoveLink(SocialMediaType.OfficialWebsite);

// V4 - Immutable
// actor.SetSocialMediaLinks(actor.SocialMediaLinks.RemoveLink(SocialMediaType.Twitter));
// actor.SetSocialMediaLinks(actor.SocialMediaLinks.RemoveLink(SocialMediaType.Instagram));
// actor.SetSocialMediaLinks(actor.SocialMediaLinks.RemoveLink(SocialMediaType.Facebook));
// actor.SetSocialMediaLinks(actor.SocialMediaLinks.RemoveLink(SocialMediaType.OfficialWebsite));

// V5
actor.SocialMediaLinks.RemoveLink(SocialMediaType.Twitter);
actor.SocialMediaLinks.RemoveLink(SocialMediaType.Instagram);
actor.SocialMediaLinks.RemoveLink(SocialMediaType.Facebook);
actor.SocialMediaLinks.RemoveLink(SocialMediaType.OfficialWebsite);

Let's try strategy pattern

Change on design:

I've employed the strategy pattern to encapsulate the implementation of each social media type within distinct classes. This method treats each media link as its own object, enhancing maintainability and flexibility. However, as demonstrated in the subsequent code, this approach results in an increased number of classes as we introduce a new class for each social media type.

  • If i need to modify business rules or validations for TwitterLink, it won't affect InstagramLink or FacebookLink implementations.
  • If i need to create a new social media link implementation like X platform, then i can create a new XLink implementation class.

Find this code in https://github.com/csharp-projects-kenanhancer/csharp-design-patterns/tree/main/Example6

public interface ISocialMediaLink
{
    SocialMediaType SocialMediaType { get; }
    string Name { get; }
    string Link { get; }
    bool Active { get; }
    string GetProfilePictureUrl();
    void SetActive(bool active);
}
public class ValidationResult
{
    public bool IsValid { get; private set; } = true;
    private readonly List<string> _errors = new List<string>();
    public IReadOnlyList<string> Errors => _errors.AsReadOnly();
    public string ErrorMessage => string.Join("\n", Errors);

    public void AddError(string error)
    {
        IsValid = false;
        _errors.Add(error);
    }

    public void Merge(ValidationResult other)
    {
        if (other.IsValid) return;

        IsValid = false;
        _errors.AddRange(other.Errors);
    }
}
public abstract class BaseSocialMediaLink : ISocialMediaLink
{
    public SocialMediaType SocialMediaType { get; }
    public string Name { get; }
    public string Link { get; }
    public bool Active { get; private set; }

    protected BaseSocialMediaLink(SocialMediaType socialMediaType, string name, string link, bool active = true)
    {
        SocialMediaType = socialMediaType;

        if (!Validate(name, link, out ValidationResult result))
            throw new InvalidOperationException(result.ErrorMessage);

        Name = name;
        Link = link;
        Active = active;
    }

    public virtual string GetProfilePictureUrl() => $"{Link}/profile-pic";

    public virtual void SetActive(bool active) => Active = active;

    protected virtual bool IsValidName(string name, out ValidationResult result)
    {
        result = new ValidationResult();

        return result.IsValid;
    }

    protected virtual bool IsValidLink(string link, out ValidationResult result)
    {
        result = new ValidationResult();

        return result.IsValid;
    }

    private bool Validate(string name, string link, out ValidationResult result)
    {
        result = new ValidationResult();

        if (!ValidateName(name, out ValidationResult nameResult))
            result.Merge(nameResult);
        if (!ValidateLink(link, out ValidationResult linkResult))
            result.Merge(linkResult);

        return result.IsValid;
    }

    private bool ValidateName(string name, out ValidationResult result)
    {
        result = new ValidationResult();

        if (String.IsNullOrWhiteSpace(name))
            result.AddError($"${name} cannot be empty.");

        if (name.Length < 3)
            result.AddError($"{nameof(name)} must be at least 3 characters long.");

        if (name.Length > 50)
            result.AddError($"{nameof(name)} must not exceed 20 characters.");

        if (name.StartsWith(" ") || name.EndsWith(" "))
            result.AddError($"{nameof(name)} cannot start or end with a space.");

        if (name.Contains("  ")) // Checking for consecutive spaces
            result.AddError($"{nameof(name)} cannot contain consecutive spaces.");

        if (!IsValidName(name, out ValidationResult result2))
            result.Merge(result2);

        return result.IsValid;
    }

    private bool ValidateLink(string link, out ValidationResult result)
    {
        result = new ValidationResult();

        if (string.IsNullOrWhiteSpace(link))
            result.AddError($"{nameof(link)} cannot be empty.");

        if (!link.StartsWith("https://"))
            result.AddError($"{nameof(link)} must start with 'https://'.");

        if (link.Contains(" "))
            result.AddError($"{nameof(link)} cannot contain spaces.");

        if (!IsValidLink(link, out ValidationResult result2))
            result.Merge(result2);

        return result.IsValid;
    }
}
public class TwitterLink : BaseSocialMediaLink
{
    private const string TwitterDomain = "twitter.com";

    public TwitterLink(string name, string link) : base(SocialMediaType.Twitter, name, link)
    {
    }

    public override string GetProfilePictureUrl()
    {
        // Extract the screen name from the Link
        var screenName = Link.Split("/").Last();

        // Construct the profile picture URL
        return $"https://{TwitterDomain}/{screenName}/profile_image?size=original";
    }

    protected override bool IsValidLink(string link, out ValidationResult result)
    {
        base.IsValidLink(link, out result);

        if (!link.Contains(TwitterDomain))
            result.AddError($"{nameof(link)} must contain '{TwitterDomain}'.");

        string userName = ExtractUsernameFromLink(link);

        // If the profile part is not alphanumeric, return false
        if (!IsValidTwitterUsername(userName))
            result.AddError($"{nameof(link)}'s profile section after '{TwitterDomain}' should be alphanumeric.");

        return result.IsValid;
    }

    private string ExtractUsernameFromLink(string link)
    {
        // Extracting potential username or profile id after "twitter.com"
        return link.Substring(link.IndexOf(TwitterDomain, StringComparison.Ordinal) + TwitterDomain.Length + 1);
    }

    private bool IsValidTwitterUsername(string username)
    {
        return Regex.IsMatch(username, @"^[a-zA-Z0-9._-]+$");
    }
}
public class InstagramLink : BaseSocialMediaLink
{
    private const string InstagramDomain = "instagram.com";

    public InstagramLink(string name, string link) : base(SocialMediaType.Instagram, name, link)
    {
    }

    public override string GetProfilePictureUrl()
    {
        // Extract the username from the Link
        var username = Link.Split("/").Last();

        // Construct the mock profile picture URL
        return $"https://{InstagramDomain}/{username}/profile_pic_mock.jpg";
    }

    protected override bool IsValidLink(string link, out ValidationResult result)
    {
        base.IsValidLink(link, out result);

        if (!link.Contains(InstagramDomain))
            result.AddError($"{nameof(link)} must contain '{InstagramDomain}'.");

        string userName = ExtractUsernameFromLink(link);

        // If the profile part is not alphanumeric, return false
        if (!IsValidInstagramUsername(userName))
            result.AddError($"{nameof(link)}'s profile section after '{InstagramDomain}' should be alphanumeric.");

        return result.IsValid;
    }

    private string ExtractUsernameFromLink(string link)
    {
        // Extracting potential username or profile id after "instagram.com" 
        return link.Substring(link.IndexOf(InstagramDomain, StringComparison.Ordinal) + InstagramDomain.Length + 1);
    }

    private bool IsValidInstagramUsername(string username)
    {
        // Instagram usernames are alphanumeric and can contain periods and underscores
        return Regex.IsMatch(username, @"^[a-zA-Z0-9._-]+$");
    }
}
public class FacebookLink : BaseSocialMediaLink
{
    private const string FacebookDomain = "facebook.com";

    public FacebookLink(string name, string link) : base(SocialMediaType.Facebook, name, link)
    {
    }

    public override string GetProfilePictureUrl()
    {
        // Extract the username or ID from the Link
        var userId = Link.Split("/").Last();

        // Construct the mock profile picture URL
        return $"https://{FacebookDomain}/{userId}/profile_pic_mock.jpg";
    }

    protected override bool IsValidLink(string link, out ValidationResult result)
    {
        base.IsValidLink(link, out result);

        if (!link.Contains(FacebookDomain))
            result.AddError($"{nameof(link)} must contain '{FacebookDomain}'.");

        // Extracting potential username or profile id after "facebook.com"
        string username = ExtractUsernameFromLink(link);

        // If the profile part is not alphanumeric, return false
        if (!IsValidFacebookUsername(username))
            result.AddError($"{nameof(link)}'s profile section after '{FacebookDomain}' should be alphanumeric.");

        return result.IsValid;
    }

    private string ExtractUsernameFromLink(string link)
    {
        // Extracting potential username or profile id after "facebook.com"
        return link.Substring(link.IndexOf(FacebookDomain, StringComparison.Ordinal) + FacebookDomain.Length + 1);
    }

    private bool IsValidFacebookUsername(string username)
    {
        // Facebook usernames are alphanumeric and can contain periods and underscores
        return Regex.IsMatch(username, @"^[a-zA-Z0-9._-]+$");
    }
}
public class OfficialWebsite : BaseSocialMediaLink
{
    public OfficialWebsite(string name, string link) : base(SocialMediaType.OfficialWebsite, name, link)
    {
    }

    public override string GetProfilePictureUrl() => $"{Link}/assets/profile-pic.jpg";
}
public class SocialMediaLinks
{
    private readonly List<ISocialMediaLink> _links = new List<ISocialMediaLink>();

    public void AddLink(ISocialMediaLink link)
    {
        if (link == null)
            throw new ArgumentNullException(nameof(link));

        if (_links.Any(l => l.Link.Equals(link.Link, StringComparison.OrdinalIgnoreCase)))
            throw new ArgumentException($"Link already exists: {link.Link}");

        _links.Add(link);
    }

    public void UpdateLink(ISocialMediaLink newLink)
    {
        if (newLink == null)
            throw new ArgumentNullException(nameof(newLink));

        if (String.IsNullOrWhiteSpace(newLink.Name))
            throw new ArgumentNullException(nameof(newLink.Name));

        var existingLink = _links.FirstOrDefault(l => l.Name.Equals(newLink.Name, StringComparison.OrdinalIgnoreCase));
        if (existingLink == null)
            throw new ArgumentException($"Link not found: {newLink.Name}");

        _links.Remove(existingLink);
        _links.Add(newLink);
    }

    public void RemoveLink(string name)
    {
        if (String.IsNullOrWhiteSpace(name))
            throw new ArgumentNullException(nameof(name));

        var link = _links.FirstOrDefault(l => l.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
        if (link == null)
            throw new ArgumentException($"Link not found: {name}");

        _links.Remove(link);
    }

    public ISocialMediaLink GetLink(string name)
    {
        if (String.IsNullOrWhiteSpace(name))
            throw new ArgumentNullException(nameof(name));

        var link = _links.FirstOrDefault(l => l.Name.Equals(name, StringComparison.OrdinalIgnoreCase));

        if (link == null)
            throw new ArgumentException($"Link not found: {name}");

        return link;
    }
}
public class Actor
{
    public Guid Id { get; private set; }

    // Personal Details
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public string Biography { get; private set; }

    // Social Media Links
    public SocialMediaLinks SocialMediaLinks { get; private set; }

    public Actor(string firstName, string lastName, string biography, SocialMediaLinks socialMediaLinks)
    {
        Id = Guid.NewGuid();
        // FirstName = firstName is not null and not "" ? firstName : throw new ArgumentNullException(nameof(firstName));
        FirstName = !String.IsNullOrWhiteSpace(firstName)
            ? firstName
            : throw new ArgumentNullException(nameof(firstName));
        LastName = !String.IsNullOrWhiteSpace(lastName) ? lastName : throw new ArgumentNullException(nameof(lastName));
        Biography = !String.IsNullOrWhiteSpace(biography)
            ? biography
            : throw new ArgumentNullException(nameof(biography));
        SocialMediaLinks = socialMediaLinks ?? throw new ArgumentNullException(nameof(socialMediaLinks));
    }

    public void SetSocialMediaLinks(SocialMediaLinks newSocialMediaLinks)
    {
        SocialMediaLinks = newSocialMediaLinks ?? throw new ArgumentNullException(nameof(newSocialMediaLinks));
    }
}
var socialMediaLinks = new SocialMediaLinks();
socialMediaLinks.AddLink(new TwitterLink("John's Twitter", "https://twitter.com/johndoe"));
socialMediaLinks.AddLink(new InstagramLink("John's Instagram", "https://instagram.com/johndoe"));
socialMediaLinks.AddLink(new FacebookLink("John's Facebook", "https://facebook.com/johndoe"));
socialMediaLinks.AddLink(new OfficialWebsite("John's Official Website", "https://www.johndoe.com"));

var actor = new Actor("John", "Doe", "John Doe is an acclaimed actor...", socialMediaLinks);

// Get a specific social media link
Console.WriteLine($"Original Twitter: {actor.SocialMediaLinks.GetLink("John's Twitter").Link}");
Console.WriteLine($"Original Instagram: {actor.SocialMediaLinks.GetLink("John's Instagram").Link}");
Console.WriteLine($"Original Facebook: {actor.SocialMediaLinks.GetLink("John's Facebook").Link}");
Console.WriteLine($"Original OfficialWebsite: {actor.SocialMediaLinks.GetLink("John's Official Website").Link}");

// Update social media links for the actor 
// V1
// actor.UpdateTwitterLink("https://twitter.com/new-johndoe");
// actor.UpdateInstagramLink("https://instagram.com/new-johndoe");
// actor.UpdateFacebookLink("https://facebook.com/new-johndoe");
// actor.UpdateOfficialWebsite("https://www.new-johndoe.com");

// V2
// actor.SocialMediaLinks.UpdateTwitterLink("https://twitter.com/new-johndoe");
// actor.SocialMediaLinks.UpdateInstagramLink("https://instagram.com/new-johndoe");
// actor.SocialMediaLinks.UpdateFacebookLink("https://facebook.com/new-johndoe");
// actor.SocialMediaLinks.UpdateOfficialWebsite("https://www.new-johndoe.com");

// V3
// actor.SocialMediaLinks.UpdateLink(SocialMediaType.Twitter, "https://twitter.com/new-johndoe");
// actor.SocialMediaLinks.UpdateLink(SocialMediaType.Instagram, "https://instagram.com/new-johndoe");
// actor.SocialMediaLinks.UpdateLink(SocialMediaType.Facebook, "https://facebook.com/new-johndoe");
// actor.SocialMediaLinks.UpdateLink(SocialMediaType.OfficialWebsite, "https://www.new-johndoe.com");

// V4 - Immutable
// actor.SetSocialMediaLinks(
//     actor.SocialMediaLinks.UpdateLink(SocialMediaType.Twitter, "https://twitter.com/new-johndoe"));
// actor.SetSocialMediaLinks(
//     actor.SocialMediaLinks.UpdateLink(SocialMediaType.Instagram, "https://instagram.com/new-johndoe"));
// actor.SetSocialMediaLinks(
//     actor.SocialMediaLinks.UpdateLink(SocialMediaType.Facebook, "https://facebook.com/new-johndoe"));
// actor.SetSocialMediaLinks(
//     actor.SocialMediaLinks.UpdateLink(SocialMediaType.OfficialWebsite, "https://www.new-johndoe.com"));

// V5
// actor.SocialMediaLinks.UpdateLink(SocialMediaType.Twitter, "https://twitter.com/new-johndoe");
// actor.SocialMediaLinks.UpdateLink(SocialMediaType.Instagram, "https://instagram.com/new-johndoe");
// actor.SocialMediaLinks.UpdateLink(SocialMediaType.Facebook, "https://facebook.com/new-johndoe");
// actor.SocialMediaLinks.UpdateLink(SocialMediaType.OfficialWebsite, "https://www.new-johndoe.com");

// V6
actor.SocialMediaLinks.UpdateLink(new TwitterLink("John's Twitter", "https://twitter.com/new-johndoe"));
actor.SocialMediaLinks.UpdateLink(new InstagramLink("John's Instagram", "https://instagram.com/new-johndoe"));
actor.SocialMediaLinks.UpdateLink(new FacebookLink("John's Facebook", "https://facebook.com/new-johndoe"));
actor.SocialMediaLinks.UpdateLink(new OfficialWebsite("John's Official Website", "https://www.new-johndoe.com"));

// Get a specific social media link
Console.WriteLine($"Updated Twitter: {actor.SocialMediaLinks.GetLink("John's Twitter").Link}");
Console.WriteLine($"Updated Instagram: {actor.SocialMediaLinks.GetLink("John's Instagram").Link}");
Console.WriteLine($"Updated Facebook: {actor.SocialMediaLinks.GetLink("John's Facebook").Link}");
Console.WriteLine($"Updated OfficialWebsite: {actor.SocialMediaLinks.GetLink("John's Official Website").Link}");

// Remove a specific social media link
// V1
// actor.RemoveTwitterLink();
// actor.RemoveInstagramLink();
// actor.RemoveFacebookLink();
// actor.RemoveOfficialWebsite();

// V2
// actor.SocialMediaLinks.RemoveTwitterLink();
// actor.SocialMediaLinks.RemoveInstagramLink();
// actor.SocialMediaLinks.RemoveFacebookLink();
// actor.SocialMediaLinks.RemoveOfficialWebsite();

// V3
// actor.SocialMediaLinks.RemoveLink(SocialMediaType.Twitter);
// actor.SocialMediaLinks.RemoveLink(SocialMediaType.Instagram);
// actor.SocialMediaLinks.RemoveLink(SocialMediaType.Facebook);
// actor.SocialMediaLinks.RemoveLink(SocialMediaType.OfficialWebsite);

// V4 - Immutable
// actor.SetSocialMediaLinks(actor.SocialMediaLinks.RemoveLink(SocialMediaType.Twitter));
// actor.SetSocialMediaLinks(actor.SocialMediaLinks.RemoveLink(SocialMediaType.Instagram));
// actor.SetSocialMediaLinks(actor.SocialMediaLinks.RemoveLink(SocialMediaType.Facebook));
// actor.SetSocialMediaLinks(actor.SocialMediaLinks.RemoveLink(SocialMediaType.OfficialWebsite));

// V5
// actor.SocialMediaLinks.RemoveLink(SocialMediaType.Twitter);
// actor.SocialMediaLinks.RemoveLink(SocialMediaType.Instagram);
// actor.SocialMediaLinks.RemoveLink(SocialMediaType.Facebook);
// actor.SocialMediaLinks.RemoveLink(SocialMediaType.OfficialWebsite);

// V6
actor.SocialMediaLinks.RemoveLink("John's Twitter");
actor.SocialMediaLinks.RemoveLink("John's Instagram");
actor.SocialMediaLinks.RemoveLink("John's Facebook");
actor.SocialMediaLinks.RemoveLink("John's Official Website");

Implementing static factory methods in social media links

i have used static factory method and moved exception in it.

Find this code in https://github.com/csharp-projects-kenanhancer/csharp-design-patterns/tree/main/Example7

public class FacebookLink : BaseSocialMediaLink
{
    private const string FacebookDomain = "facebook.com";

    private FacebookLink(string name, string link) : base(SocialMediaType.Facebook, name, link)
    {
    }

    public static FacebookLink Create(string name, string link) => new(name, link);

    public override string GetProfilePictureUrl()
    {
        // Extract the username or ID from the Link
        var userId = Link.Split("/").Last();

        // Construct the mock profile picture URL
        return $"https://{FacebookDomain}/{userId}/profile_pic_mock.jpg";
    }

    protected override bool IsValidLink(string link, out ValidationResult result)
    {
        base.IsValidLink(link, out result);

        if (!link.Contains(FacebookDomain))
            result.AddError($"{nameof(link)} must contain '{FacebookDomain}'.");

        // Extracting potential username or profile id after "facebook.com"
        string username = ExtractUsernameFromLink(link);

        // If the profile part is not alphanumeric, return false
        if (!IsValidFacebookUsername(username))
            result.AddError($"{nameof(link)}'s profile section after '{FacebookDomain}' should be alphanumeric.");

        return result.IsValid;
    }

    private string ExtractUsernameFromLink(string link)
    {
        // Extracting potential username or profile id after "facebook.com"
        return link.Substring(link.IndexOf(FacebookDomain, StringComparison.Ordinal) + FacebookDomain.Length + 1);
    }

    private bool IsValidFacebookUsername(string username)
    {
        // Facebook usernames are alphanumeric and can contain periods and underscores
        return Regex.IsMatch(username, @"^[a-zA-Z0-9._-]+$");
    }
}
public class InstagramLink : BaseSocialMediaLink
{
    private const string InstagramDomain = "instagram.com";

    private InstagramLink(string name, string link) : base(SocialMediaType.Instagram, name, link)
    {
    }

    public static InstagramLink Create(string name, string link) => new(name, link);

    public override string GetProfilePictureUrl()
    {
        // Extract the username from the Link
        var username = Link.Split("/").Last();

        // Construct the mock profile picture URL
        return $"https://{InstagramDomain}/{username}/profile_pic_mock.jpg";
    }

    protected override bool IsValidLink(string link, out ValidationResult result)
    {
        base.IsValidLink(link, out result);

        if (!link.Contains(InstagramDomain))
            result.AddError($"{nameof(link)} must contain '{InstagramDomain}'.");

        string userName = ExtractUsernameFromLink(link);

        // If the profile part is not alphanumeric, return false
        if (!IsValidInstagramUsername(userName))
            result.AddError($"{nameof(link)}'s profile section after '{InstagramDomain}' should be alphanumeric.");

        return result.IsValid;
    }

    private string ExtractUsernameFromLink(string link)
    {
        // Extracting potential username or profile id after "instagram.com" 
        return link.Substring(link.IndexOf(InstagramDomain, StringComparison.Ordinal) + InstagramDomain.Length + 1);
    }

    private bool IsValidInstagramUsername(string username)
    {
        // Instagram usernames are alphanumeric and can contain periods and underscores
        return Regex.IsMatch(username, @"^[a-zA-Z0-9._-]+$");
    }
}
public class TwitterLink : BaseSocialMediaLink
{
    private const string TwitterDomain = "twitter.com";

    private TwitterLink(string name, string link) : base(SocialMediaType.Twitter, name, link)
    {
    }

    public static TwitterLink Create(string name, string link) => new(name, link);

    public override string GetProfilePictureUrl()
    {
        // Extract the screen name from the Link
        var screenName = Link.Split("/").Last();

        // Construct the profile picture URL
        return $"https://{TwitterDomain}/{screenName}/profile_image?size=original";
    }

    protected override bool IsValidLink(string link, out ValidationResult result)
    {
        base.IsValidLink(link, out result);

        if (!link.Contains(TwitterDomain))
            result.AddError($"{nameof(link)} must contain '{TwitterDomain}'.");

        string userName = ExtractUsernameFromLink(link);

        // If the profile part is not alphanumeric, return false
        if (!IsValidTwitterUsername(userName))
            result.AddError($"{nameof(link)}'s profile section after '{TwitterDomain}' should be alphanumeric.");

        return result.IsValid;
    }

    private string ExtractUsernameFromLink(string link)
    {
        // Extracting potential username or profile id after "twitter.com"
        return link.Substring(link.IndexOf(TwitterDomain, StringComparison.Ordinal) + TwitterDomain.Length + 1);
    }

    private bool IsValidTwitterUsername(string username)
    {
        return Regex.IsMatch(username, @"^[a-zA-Z0-9._-]+$");
    }
}
public class OfficialWebsite : BaseSocialMediaLink
{
    private OfficialWebsite(string name, string link) : base(SocialMediaType.OfficialWebsite, name, link)
    {
    }

    public static OfficialWebsite Create(string name, string link) => new(name, link);

    public override string GetProfilePictureUrl() => $"{Link}/assets/profile-pic.jpg";
}
var socialMediaLinks = new SocialMediaLinks();
socialMediaLinks.AddLink(TwitterLink.Create("John's Twitter", "https://twitter.com/johndoe"));
socialMediaLinks.AddLink(InstagramLink.Create("John's Instagram", "https://instagram.com/johndoe"));
socialMediaLinks.AddLink(FacebookLink.Create("John's Facebook", "https://facebook.com/johndoe"));
socialMediaLinks.AddLink(OfficialWebsite.Create("John's Official Website", "https://www.johndoe.com"));

var actor = new Actor("John", "Doe", "John Doe is an acclaimed actor...", socialMediaLinks);

// Get a specific social media link
Console.WriteLine($"Original Twitter: {actor.SocialMediaLinks.GetLink("John's Twitter").Link}");
Console.WriteLine($"Original Instagram: {actor.SocialMediaLinks.GetLink("John's Instagram").Link}");
Console.WriteLine($"Original Facebook: {actor.SocialMediaLinks.GetLink("John's Facebook").Link}");
Console.WriteLine($"Original OfficialWebsite: {actor.SocialMediaLinks.GetLink("John's Official Website").Link}");

// Update social media links for the actor
actor.SocialMediaLinks.UpdateLink(TwitterLink.Create("John's Twitter", "https://twitter.com/new-johndoe"));
actor.SocialMediaLinks.UpdateLink(InstagramLink.Create("John's Instagram", "https://instagram.com/new-johndoe"));
actor.SocialMediaLinks.UpdateLink(FacebookLink.Create("John's Facebook", "https://facebook.com/new-johndoe"));
actor.SocialMediaLinks.UpdateLink(OfficialWebsite.Create("John's Official Website", "https://www.new-johndoe.com"));

// Get a specific social media link
Console.WriteLine($"Updated Twitter: {actor.SocialMediaLinks.GetLink("John's Twitter").Link}");
Console.WriteLine($"Updated Instagram: {actor.SocialMediaLinks.GetLink("John's Instagram").Link}");
Console.WriteLine($"Updated Facebook: {actor.SocialMediaLinks.GetLink("John's Facebook").Link}");
Console.WriteLine($"Updated OfficialWebsite: {actor.SocialMediaLinks.GetLink("John's Official Website").Link}");

// Remove a specific social media link
actor.SocialMediaLinks.RemoveLink("John's Twitter");
actor.SocialMediaLinks.RemoveLink("John's Instagram");
actor.SocialMediaLinks.RemoveLink("John's Facebook");
actor.SocialMediaLinks.RemoveLink("John's Official Website");