3

Could you say splitting a class into partial classes is a way to encapsulate your code?

Also, why is splitting a code into partial classes not a part of a design pattern? I mean you kind of design the code by splitting it up for a better overview, right?

General.cs:

/*  SINGLETON DESIGN PATTERN (slides and https://sourcemaking.com/design_patterns/singleton)

     * INTENT
       - Ensure a class has only one instance, and provide a global point of access to it.
       - Encapsulated "just-in-time initialization" or "initialization on first use".

      * ABOUT
       - Make the class of the single instance object responsible for creation, initialization,
       access, and enforcement.

 */
namespace SpaceTaxi_3.States.GameRunning.Parser {
    public partial class LevelParser {

        private static LevelParser instance;

        public static LevelParser GetInstance() {
            return LevelParser.instance ?? (LevelParser.instance = new LevelParser());
        }

        public Dictionary<char, string> Obstacles;
        public Dictionary<char, string> Platforms;
        public Dictionary<char, string> Exits;
        public Dictionary<string, Tuple<int, char, string, int, int, Entity>> Costumer;
        public List<Entity> ObstacleEntities { get; set; }
        public List<Platform> PlatformEntities { get; set; }
        public List<Entity> ExitEntities { get; set; }

        public string[] LevelFile;

        // Wrapper
        public void Load(string fileName) {

            // Catches incorrect files here
            if (ValidMapCheck(fileName)) {
                PlatformEntities = new List<Platform>();
                ExitEntities = new List<Entity>();
                ObstacleEntities = new List<Entity>();

                Platforms = new Dictionary<char, string>();
                Exits = new Dictionary<char, string>();
                Obstacles = new Dictionary<char, string>();
                Costumer = new Dictionary<string, Tuple<int, char, string, int, int, Entity>>();

                LevelFile = ReadFile("Levels", fileName);

                Platforms = GetPlatforms(LevelFile);
                Exits = GetExits(LevelFile);
                Obstacles = GetObstacles(LevelFile);
                Costumer = GetCustomerInfo(LevelFile);

                AddEntities(GetAllTiles(LevelFile));
            }
        }

        /// <summary>
        ///  Checks whether a file exists or not
        /// </summary>
        private void ValidatePath(string file) {
            if (!File.Exists(file)) {
                throw new FileNotFoundException($"Error: " +
                                                $"The path to \"{file}\" does not exist.");
            }
        }

        /// <summary>
        ///  Finds full path to directory (e.g. directoryName: "Levels" or "Assets")
        ///  Starts from /bin/Debug folder, then goes to parent /bin, and so on.
        ///  Casts an exception if we iterated down to the root of the tree.
        /// </summary>
        private string GetPath(string directoryName) {
            DirectoryInfo dir = new DirectoryInfo(Path.GetDirectoryName(System.Reflection.Assembly.
                GetExecutingAssembly().Location));

            while (dir.Name != directoryName) {
                if (dir.FullName == dir.Root.Name) {
                    throw new FileNotFoundException(
                        $"Error: Directory \"{directoryName}\" does not exist.");
                } else {
                    foreach (var i in dir.GetDirectories()) {
                        if (i.Name == directoryName) {
                            return i.FullName;
                        }
                    }

                    dir = dir.Parent;
                }
            }
            return dir.FullName;
        }

        /// <summary>
        /// Making sure our mapfiles are not tampered with by calculating the checksum
        /// Source: https://stackoverflow.com/questions/10520048/calculate-md5-checksum-for-a-file
        /// Source: https://en.wikipedia.org/wiki/MD5
        /// </summary>
        /// <param name="filename"></param>
        public string CheckMD5(string filename)
        {
            // "using": automatically disposes the object after use,
            //  even if exception is casted
            using (var md5 = MD5.Create())
            {
                using (var stream = File.OpenRead(filename))
                {
                    var hash = md5.ComputeHash(stream);
                    return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
                }
            }
        }

        /// <summary>
        ///  Checks for invalid files and invalid filecontent
        /// </summary>
        public bool ValidMapCheck(string filename) {

            var hash = "";
            var hash32bit = "";
            Console.WriteLine("Hash: " + CheckMD5(Path.Combine(GetPath("Levels"), filename)));

            // Checks for invalid files
            if (filename != "the-beach.txt" && filename != "short-n-sweet.txt") {
                throw new ArgumentException($"Parser cannot load: {filename}");
            }

            // Checks for invalid file content
            if (filename == "the-beach.txt") {
                hash = "81b89b1908e3b3b7fcd7526810c32f14";
                hash32bit = "34d0e9c5ea54bfc60a0365f28b7d3a19";
            }
            if (filename == "short-n-sweet.txt") {
                hash = "e97f28bfff174f9643c088814377ada6";
                hash32bit = "5c4832a9a5510bdab5976ad0e6905e85";

            }

            var checksum = CheckMD5(Path.Combine(GetPath("Levels"), filename));
            if (checksum == hash || checksum == hash32bit) {
                return true;
            }
            throw new ArgumentException($"You've tampered with {filename}!");
            // Todo: Find out how to hide these hash strings and to avoid inline hardcode
        }

        /// <summary>
        ///  Collects a string[] containing all strings in the file
        /// </summary>
        private string[] ReadFile(string directoryName, string fileName) {
            var dir = GetPath(directoryName);
            string path = Path.Combine(dir, fileName);
            ValidatePath(path);
            return File.ReadAllLines(path);
        }

        /// <summary>
        ///  Extracts ASCII characters from the txt file and appends it
        ///  to an array. Later we will use this array to draw pictures.
        /// </summary>
        private List<char> GetAllTiles(string[] txtFile) {
            var charList = new List<char>();
            for (int i = 0; i < 23; i++) {
                foreach (var j in txtFile[i]) {
                    charList.Add(j);
                }
            }

            return charList;
        }

        /// <summary>
        ///    Adds all objects to the appropriate map being loaded
        ///    We iterate from top-left (0,1) of the screen to bottom-right (1,0)
        /// </summary>
        private void AddEntities(List<char> map) {
            float tmpX = Constants.X_MIN;
            float tmpY = Constants.Y_MAX;

            int index = 0;

            // Going from top (y 1.0) to bottom (y 0.0)
            while (tmpY > Constants.Y_MIN) {

                // Going from left (x 0.0) to right (x 1.0)
                while (tmpX < Constants.X_MAX) {

                    // There can be empty strings in our list of strings.
                    if (map[index].ToString() == " ") {
                        index += 1;

                    } else {
                        // Adds obstacles
                        if (Obstacles.ContainsKey(map[index])) {
                            var shape = new StationaryShape(new Vec2F(tmpX, tmpY),
                                new Vec2F(Constants.WIDTH, Constants.HEIGHT));
                            var file = Path.Combine(GetPath("Assets"),
                                "Images", Obstacles[map[index]]);
                            ValidatePath(file);
                            ObstacleEntities.Add(new Entity(shape, new Image(file)));
                        }

                        // Adds platforms
                        if (Platforms.ContainsKey(map[index])) {
                            var shape = new StationaryShape(new Vec2F(tmpX, tmpY),
                                new Vec2F(Constants.WIDTH, Constants.HEIGHT));
                            var file = Path.Combine(GetPath("Assets"), "Images",
                                Platforms[map[index]]);
                            ValidatePath(file);
                            PlatformEntities.Add(new Platform(shape, new Image(file), map[index]));
                        }

                        // Adds exits
                        if (Exits.ContainsKey(map[index])) {
                            var shape = new StationaryShape(new Vec2F(tmpX, tmpY),
                                new Vec2F(Constants.WIDTH, Constants.HEIGHT));
                            var file = Path.Combine(GetPath("Assets"), "Images",
                                Exits[map[index]]);
                            ValidatePath(file);
                            ExitEntities.Add(new Entity(shape, new Image(file)));
                        }

                        // Update index
                        index += 1;
                    }

                    tmpX += Constants.WIDTH;
                }

                tmpX = 0;
                tmpY -= Constants.HEIGHT;
            }
        }
    }
}

ParseCustomer.cs:

public partial class LevelParser {
    public Dictionary<string, Tuple<int, char, string, int, int, Entity>>
        GetCustomerInfo(string[] txtFile) {

        // Creates a dictionary containing all info about the customer
        var retDict = new Dictionary<string, Tuple<int, char, string, int, int, Entity>>();

        // Iterates over the entire txtFile and finds certain string
        IEnumerable<string> findName = txtFile.Where(l => l.StartsWith("Customer: "));
        foreach (var line in findName)
        {
            // Parses file
            var name = line.Split(' ')[1];
            var timeBeforeSpawn = Convert.ToInt32(line.Split(' ')[2]); // seconds
            var spawnOnPlatform = Convert.ToChar(line.Split(' ')[3]);
            var destinationPlat = line.Split(' ')[4]; // string
            var patienceTime = Convert.ToInt32(line.Split(' ')[5]); // seconds
            var rewardPoints = Convert.ToInt32(line.Split(' ')[6]);

            // Adds an entity
            var shape = new DynamicShape(new Vec2F(), new Vec2F());
            var image = new Image(Path.Combine("Assets", "Images", "CustomerStandLeft.png"));
            var entity = new Entity(shape, image);

            retDict.Add(name, new Tuple<int, char, string, int, int, Entity>(timeBeforeSpawn,
                spawnOnPlatform, destinationPlat, patienceTime, rewardPoints, entity));

        }

        // Now we have everything we need in one dictionary
        return retDict;
    }
}

There are 3 more files each containing the same partial class but has a method which does something different than the others.

Jota.Toledo
  • 27,293
  • 11
  • 59
  • 73
jubibanna
  • 1,095
  • 9
  • 21
  • 5
    Design patterns are more about how your code can be _used_ and less about how your code is _written_. Whether you write a single class across 100 files or 1 file, it's not going to make a difference to a caller of the class. – Sweeper Jun 07 '19 at 05:41
  • 2
    Partial classes are organizational sugar. Think of winforms: partial classes lets you separate tool/designer-managed code from manually modified code. And that's the most useful application I can think of. If you feel the need to split a non-gui class into partial classes, I would be triggered to check if I should not better refactor. For me that's a hint for "too many responsibilities". All of which has nothing to do with design patterns whatsoever. – Fildor Jun 07 '19 at 05:57

4 Answers4

3

A Partial classes role in this world is for code generators and Designers. Also as a developer you can take advantage of extending classes without messing with generated code.

However, their uses beyond this is limited and fairly suspect.

As for design patterns, design pattern in general is a reusable solution to a commonly occurring problem within a given context in software design. Are partial classes design patterns? hrmm, well not really, just like extension methods in C# are not.

All they are is a bit of syntactic sugar that are compiled to the one class anyway.

For example

public partial class Test
{
    public int testing1 {get;set;}
}

public partial class Test
{    
    public int testing2 {get;set;}
}

Gets generated as this

public class Test
{
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private int <testing1>k__BackingField;

    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private int <testing2>k__BackingField;

    public int testing1
    {
        [CompilerGenerated]
        get
        {
            return <testing1>k__BackingField;
        }
        [CompilerGenerated]
        set
        {
            <testing1>k__BackingField = value;
        }
    }

    public int testing2
    {
        [CompilerGenerated]
        get
        {
            return <testing2>k__BackingField;
        }
        [CompilerGenerated]
        set
        {
            <testing2>k__BackingField = value;
        }
    }
}
TheGeneral
  • 79,002
  • 9
  • 103
  • 141
  • To point out that partial classes are a concept on compiler level is the key to understand the difference to a design pattern that never relies on compiler details but common concepts of e.g. OO languages. Singleton Pattern for instance relies on the OO concept of static types and doesn't rely on compiler details like how the compiler realizes this feature. – BionicCode Jun 07 '19 at 09:20
1

partial class'es are simply a way to split the definition of a class across multiple files.

It is not part of a design pattern since it has no actual effect on the functionality of the class compared to a "regular" class defined in a single file.

Avi Meltser
  • 409
  • 4
  • 11
  • Your definition of a design pattern is not correct. A design pattern is not identified by looking at the effect it has on classes. When having a class `Collection`, the changing from Factory Pattern to Builder Pattern won't affect this class. And still they are design patterns. – BionicCode Jun 07 '19 at 08:37
  • There are different categories of design pattern. The basic ones are Behavioral, Structural, Creational and Arcitectorial Design Patterns. The partial class concept must fall at least in one of these categories. Furthermore partial classes are not a _pattern_. It's a compiler feature. A pattern is like a template of how to do things or how to accomplish a goal. – BionicCode Jun 07 '19 at 08:40
1

Could you say splitting a class into partial classes is a way to encapsulate your code?

Encapsulation refers to a mechanism of bundling the data with the methods (or other functions) operating on that data and thereby restricting direct access to some of the object's components.

whereas,

Partial classes provide a special ability to implement the functionality of a single class into multiple files and all these files are combined into a single class file when the application is compiled.

Partial classes therefore, take care of the physical distribution of code across files while encapsulation takes care of logical grouping and classification of data and behaviours meant to deal with that data.

why is splitting a code into partial classes not a part of a design pattern?

Design patterns aim to provide reusable solution to a common recurring software design problem. These patterns work at a logical level to provide solutions for the creation of an object, definition of its structure or establishment of its behavior with other entities which conform to SOLID principles.

When we are splitting a class into multiple files, we are not doing any logical change to the class that affects its relationship with any other class. Hence partial classes are not considered as design patterns.

akg179
  • 1,287
  • 1
  • 6
  • 14
  • SOLID is not a requirement for design patterns to work. I can use the Factory Pattern with or without Dependency Inversion. – BionicCode Jun 07 '19 at 09:04
  • @BionicCode I have mentioned it to 'conform' to SOLID principles. It does not mean that design patterns have to use SOLID principles always. – akg179 Jun 07 '19 at 09:05
  • But I think your explanation is one of the better ones – BionicCode Jun 07 '19 at 09:05
  • I understood. But SOLID has no influence whether a design pattern can be applied or not. Patterns also work with entities that do not conform to SOLID. – BionicCode Jun 07 '19 at 09:08
  • I gave you an upvote, but this detail is not correct. – BionicCode Jun 07 '19 at 09:12
  • @BionicCode Thanks for the upvote. I m not sure if i have used the correct words but what I have meant above is that design patterns are created keeping few principles in mind like 'single responsibility', 'open for extension but closed for modification', 'interface segregation' etc. which are all aggregated and presented to us as SOLID principles. If a pattern is violating any SOLID principle, then I don't think it is a good pattern to go with. – akg179 Jun 07 '19 at 09:23
  • I agree that some patterns, maybe most, are motivated by the same concerns that SOLID is addressing. But it's wrong to say design patterns depend on SOLID. We can't generalize at this point. When having an architectural problem, you could for example choose between the MVC, MVP or an MVVM like pattern. All of them don't care about Liskov, Dependency Inversion, Interface Segregation or SRP and Open-Close. They are operating on a different level, solving different problems like SoC or deployment issues (e.g. in a server-client environment). – BionicCode Jun 07 '19 at 09:56
  • Design patterns and SOLID coexist but they don't depend on each other by any definition. And to violate SOLID is not 'forbidden'. SOLID always comes with trade offs like implementation time or complexity. There are always exceptions to rules and of course to principles. Principles are weaker than rules or laws. The Visitor Pattern does violate the Open-Close Principle, but has successfully solved the problem to make code extensible. It's a valid and very famous pattern. – BionicCode Jun 07 '19 at 09:56
  • @BionicCode Its a debatable thing that MVC, MVP, MVVM are architectural patterns or design patterns, however, just to clarify, for my answer I have considered the GOF design patterns. Also I read about Visitor pattern, and I found several articles (including Wikipedia) which say that Visitor pattern is one way to follow OCP or makes code OCP compliant. Not intending to debate further on that. Just to conclude: You agreed that 'some patterns, maybe most' are motivated by SOLID and my intention of writing 'patterns conform to SOLID principles' has been the same. :) – akg179 Jun 07 '19 at 11:06
0

A partial class is still a single class but spread over multiple files. The user of a partial class doesn't realize the fact that this class is partial. Encapsulation on the other hand would introduce more classes instead. Encapsulation is a structural design principle on design level and partial classes a compiler or linker concept on compiler level.

A pattern must have an impact on the design of the code's structure, behavior or architecture to be considered a design pattern. Commonly design patterns are categorized in Creational, Structural, Architectural and Behavioral Design Patterns. The concept of partial classes doesn't fall into any of those categories. The partial class concept depends on a compiler or linker to know this concept.

What makes a pattern a pattern? A pattern is a template of how to do things or how to accomplish the same goal (solve the same problem) in different scenarios. A pattern is independent from the compiler platform since it is a general solution that is language independent.

Partial classes where introduced to the C# language to provide a way to separate UI markup code (XAML) from the corresponding code-behind (C#). The compiler then would merge the two files. In the first step the XAML code is converted to C# by the XAML interpreter. Then the two partial classes are merged before translated to IL.

BionicCode
  • 1
  • 4
  • 28
  • 44