1class Material:
2 def __init__(self, density, mass):
3 self.density = density
4 self.mass = mass
5 self.base_materials = None
6 self.was_used = False
7
8 @property
9 def volume(self):
10 return self.mass / self.density
11
12 def is_composite(self):
13 return self.base_materials is not None
14
15 @staticmethod
16 def subclass_from_name(subclass_name):
17 for subclass in Material.__subclasses__():
18 if subclass.__name__ == subclass_name:
19 return subclass
20
21 return type(subclass_name, (Material,), {"__init__": lambda self, mass: (setattr(self, "mass", mass),
22 setattr(self, "base_materials", None),
23 setattr(self, "was_used", False),
24 None)[3]})
25
26 @staticmethod
27 def __get_new_base_materials(materials):
28 new_base_materials = {}
29
30 for material in materials:
31 assert not material.was_used, "Given material has already been used"
32 material.was_used = True
33
34 if material.is_composite():
35 for base_material in material.base_materials.values():
36 if new_base_materials.get(base_material.__class__) is None:
37 new_base_materials[base_material.__class__] = base_material
38 else:
39 new_base_materials[base_material.__class__].mass += base_material.mass
40 else:
41 if new_base_materials.get(material.__class__) is None:
42 new_base_materials[material.__class__] = material
43 else:
44 new_base_materials[material.__class__].mass += material.mass
45
46 return new_base_materials
47
48 @staticmethod
49 def create_composite_material(*args):
50 assert all(isinstance(arg, Material) for arg in args)
51 assert all(not arg.was_used for arg in args), "Given material has already been used"
52
53 if len(args) == 1:
54 if not args[0].was_used:
55 return args[0]
56 else:
57 raise AssertionError("Given material has already been used")
58
59 new_base_materials = Material.__get_new_base_materials(args)
60
61 new_base_names = sorted({base_material_class.__name__ for base_material_class in new_base_materials})
62 composite_class_name = "_".join(new_base_names)
63
64 composite_density = (sum([base_material.density for base_material in new_base_materials.values()])
65 / len(new_base_materials))
66 composite_mass = sum([base_material.mass for base_material in new_base_materials.values()])
67
68 composite_material = Material.subclass_from_name(composite_class_name)(composite_mass)
69 composite_material.density = composite_density
70 composite_material.base_materials = new_base_materials
71
72 return composite_material
73
74
75class Concrete(Material):
76 def __init__(self, mass):
77 super().__init__(2500, mass)
78
79
80class Brick(Material):
81 def __init__(self, mass):
82 super().__init__(2000, mass)
83
84
85class Stone(Material):
86 def __init__(self, mass):
87 super().__init__(1600, mass)
88
89
90class Wood(Material):
91 def __init__(self, mass):
92 super().__init__(600, mass)
93
94
95class Steel(Material):
96 def __init__(self, mass):
97 super().__init__(7700, mass)
98
99
100class Factory:
101 all_factories = []
102
103 def __init__(self):
104 self.own_materials = []
105 self.__class__.all_factories.append(self)
106
107 def can_build(self, target):
108 cumulative_volume = 0
109
110 for material in self.own_materials:
111 if not material.was_used:
112 cumulative_volume += material.volume
113
114 return cumulative_volume >= target
115
116 @classmethod
117 def can_build_together(cls, target):
118 cumulative_volume = 0
119
120 for factory in cls.all_factories:
121 for material in factory.own_materials:
122 cumulative_volume += material.volume
123
124 return cumulative_volume >= target
125
126 def __call__(self, *args, **kwargs):
127 if args and kwargs:
128 raise ValueError("Cannot call factory object with positional and named arguments")
129
130 if args:
131 composite_material = Material.create_composite_material(*args)
132 self.own_materials.append(composite_material)
133
134 return composite_material
135
136 elif kwargs:
137 materials = []
138 for key, value in kwargs.items():
139 subclass_found = False
140 for subclass in Material.__subclasses__():
141 if key == subclass.__name__:
142 materials.append(subclass(value))
143 subclass_found = True
144 break
145
146 if not subclass_found:
147 raise ValueError("Unknown material {}".format(key))
148
149 self.own_materials.extend(materials)
150 return tuple(materials)
151
152 else:
153 raise ValueError("Cannot call factory object with no arguments")
.FF..E.F..
======================================================================
ERROR: test_positional_arguments_multiple_argument_from_initial_set (test.TestFactory.test_positional_arguments_multiple_argument_from_initial_set)
Test calling a factory using multiple positional arguments.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 98, in test_positional_arguments_multiple_argument_from_initial_set
self.assertEqual(brick_concrete_wood.volume, 1.0)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 10, in volume
return self.mass / self.density
^^^^^^^^^^^^
AttributeError: 'Brick_Concrete_Wood' object has no attribute 'density'
======================================================================
FAIL: test_can_build (test.TestFactory.test_can_build)
Test can_build methods.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 177, in test_can_build
self.assertFalse(solution.Factory.can_build_together(6.0001))
AssertionError: True is not false
======================================================================
FAIL: test_materials_between_factories (test.TestFactory.test_materials_between_factories)
Test materials sharing.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 161, in test_materials_between_factories
with self.assertRaises(AssertionError):
AssertionError: AssertionError not raised
======================================================================
FAIL: test_positional_arguments_single_argument (test.TestFactory.test_positional_arguments_single_argument)
Test calling a factory using a sigle positional argument.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 83, in test_positional_arguments_single_argument
with self.assertRaises(AssertionError):
AssertionError: AssertionError not raised
----------------------------------------------------------------------
Ran 10 tests in 0.012s
FAILED (failures=3, errors=1)
Георги Кунчев
22.11.2024 10:24Да съм честен - `__get_new_base_materials` ме обърка. Използваш го хем да събереш класовете, хем масите на инстанциите. Бих направил така, че един алгоритъм да събере имената на клсасовете, а друг само да събере масите. Ще се улесни. Ще имаш single responsibility.
Като цяло ми харесва, че си направил така, че `Material` да върши повечето работа. Фабриката е доста проста. Но пък материала става доста сложен.
|
22.11.2024 09:53
22.11.2024 10:08
22.11.2024 09:59
22.11.2024 10:06
22.11.2024 10:09
22.11.2024 10:11
22.11.2024 10:13