Compare commits

..

3 Commits

Author SHA1 Message Date
8cd949d79e subscribers - done 2026-03-07 16:37:13 +03:00
6aea45825d reporter - done 2026-03-07 00:07:48 +03:00
e98d6998dc builders - done 2026-03-06 22:57:13 +03:00
12 changed files with 181 additions and 36 deletions

View File

@@ -185,17 +185,17 @@
Реализовать билдер для умного дома, позволяющий инициализировать объект умного дома в [таком стиле](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=5d0527e4684f726d54dc375829d983f4).
- [ ] До добавления первой комнаты, билдер запрещает добавлять устройства. Это должно контролироваться компилятором.
- [x] До добавления первой комнаты, билдер запрещает добавлять устройства. Это должно контролироваться компилятором.
Реализовать компоновщик для построения отчёта об объектах умного дома в [таком стиле](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c07dfc726e8ccbccdcc2d88a79d3f190).
- [ ] Использовать статический полиморфизм (дженерики).
- [ ] Вызов метода report() должен выводить в терминал отчёт обо всех добавленных объектах.
- [x] Использовать статический полиморфизм (дженерики).
- [x] Вызов метода report() должен выводить в терминал отчёт обо всех добавленных объектах.
Добавить возможность добавления callback-ов в объект комнаты, которые срабатывают при добавлении новых устройств в комнату (паттерн Observer).
- [ ] Использовать динамический полиморфизм (трейт-объекты).
- [ ] Можно передавать как объект-subscriber, так и [замыкание](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=06e9dc9bcce297d1e80a22d7e9338ee8).
- [x] Использовать динамический полиморфизм (трейт-объекты).
- [x] Можно передавать как объект-subscriber, так и [замыкание](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=06e9dc9bcce297d1e80a22d7e9338ee8).
Добавить example-ы, демонстрирующие новый функционал.
@@ -205,3 +205,9 @@
- Приложение-пример успешно выполняется.
- Команды cargo clippy и cargo fmt --check не выводят ошибок и предупреждений.
- Присутствуют и успешно выполняются модульные тесты.
### Демонстационные примеры
- `src/bin/house_builder.rs` - билдер для умного дома
- `src/bin/reporter.rs` - компоновщик для построения отчета
- `src/bin/subscribers.rs` - добавление коллбеков в объект комнаты

View File

@@ -0,0 +1,11 @@
use smart_house::{HouseBuilder, PowerSocket, PrintStatus, Thermometer};
fn main() {
let house = HouseBuilder::new()
.add_room("Main")
.add_device("PSockA", PowerSocket::stub(12.0, false))
.add_room("Hall")
.add_device("ThermA", Thermometer::stub(18.5))
.build();
house.print_status();
}

View File

@@ -0,0 +1,17 @@
use smart_house::{HouseBuilder, PowerSocket, Reporter, Thermometer};
fn main() {
let house = HouseBuilder::new()
.add_room("Main")
.add_device("PSockB", PowerSocket::stub(12.0, false))
.add_room("Hall")
.add_device("ThermB", Thermometer::stub(18.5))
.build();
Reporter::new()
.add_reportable(house.get_room("Main").unwrap())
.add_reportable(house.get_device("Main", "PSockB").unwrap())
.add_reportable(house.get_room("Hall").unwrap())
.add_reportable(house.get_device("Hall", "ThermB").unwrap())
.report();
}

View File

@@ -0,0 +1,22 @@
use smart_house::{Device, PowerSocket, Room, Subscriber};
fn main() {
let mut room = Room::default();
room.subscribe(MySubscriber::default());
room.subscribe(|dev: &Device| {
println!("device added: {}", dev.display());
});
room.insert_device("", PowerSocket::stub(12.0, false).into());
}
#[derive(Default)]
struct MySubscriber {}
impl Subscriber for MySubscriber {
fn on_event(&mut self, dev: &Device) {
println!("DEVICE ADDED: {}", dev.display());
}
}

View File

@@ -1,51 +1,53 @@
use crate::{Device, House, Room};
use std::collections::HashMap;
struct HouseBuilder {
pub struct HouseBuilder {
rooms: HashMap<String, Room>,
}
impl HouseBuilder {
fn new() -> Self {
Self {
rooms: HashMap::new()
}
pub fn new() -> Self {
Self { rooms: HashMap::new() }
}
fn build(self) -> House {
House::new(self.rooms)
}
fn add_room(self, name: &str) -> RoomBuilder {
pub fn add_room(self, name: &str) -> RoomBuilder {
RoomBuilder {
parent: self,
name: name.to_string(),
devices: HashMap::new()
devices: HashMap::new(),
}
}
pub fn build(self) -> House {
House::new(self.rooms)
}
}
struct RoomBuilder {
impl Default for HouseBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct RoomBuilder {
parent: HouseBuilder,
name: String,
devices: HashMap<String, Device>,
}
impl RoomBuilder {
fn add_device(mut self, name: &str, device: Device) -> Self {
self.devices.insert(name.to_string(), device);
pub fn add_device(mut self, name: &str, device: impl Into<Device>) -> Self {
self.devices.insert(name.to_string(), device.into());
self
}
fn add_room(mut self, name: &str) -> RoomBuilder {
pub fn add_room(mut self, name: &str) -> RoomBuilder {
self.parent.rooms.insert(self.name, Room::new(self.devices));
self.parent.add_room(name)
}
fn build(mut self) -> House {
pub fn build(mut self) -> House {
self.parent.rooms.insert(self.name, Room::new(self.devices));
self.parent.build()
}
}

View File

@@ -1,5 +1,6 @@
use crate::PrintStatus;
use std::fmt::Display;
use std::io::Write;
#[derive(Debug)]
pub enum Device {
@@ -33,8 +34,8 @@ impl From<super::PowerSocket> for Device {
}
impl PrintStatus for Device {
fn print_status(&self) {
println!("{}", self.display());
fn print_status_into(&self, out: &mut dyn Write) -> Result<(), std::io::Error> {
out.write_fmt(format_args!("{}", self.display()))
}
}

View File

@@ -1,5 +1,6 @@
use crate::{Device, Error, PrintStatus, Room};
use std::collections::HashMap;
use std::io::Write;
#[derive(Debug)]
pub struct House {
@@ -39,13 +40,15 @@ impl House {
}
impl PrintStatus for House {
fn print_status(&self) {
fn print_status_into(&self, out: &mut dyn Write) -> Result<(), std::io::Error> {
for (room_name, room) in self.rooms.iter() {
println!("{}:", room_name);
println!("{}", "-".repeat(32));
room.print_status();
out.write_fmt(format_args!("{}:", room_name))?;
out.write_fmt(format_args!("{}", "-".repeat(32)))?;
room.print_status_into(out)?;
out.write_fmt(format_args!("\n"))?;
println!();
}
Ok(())
}
}

View File

@@ -6,12 +6,17 @@ mod power_socket;
mod room;
mod builders;
mod print_status;
mod reporter;
mod subscriber;
mod thermometer;
pub use builders::{HouseBuilder, RoomBuilder};
pub use device::Device;
pub use error::Error;
pub use house::House;
pub use power_socket::PowerSocket;
pub use print_status::PrintStatus;
pub use reporter::Reporter;
pub use room::Room;
pub use subscriber::Subscriber;
pub use thermometer::Thermometer;

View File

@@ -1,3 +1,9 @@
pub trait PrintStatus {
fn print_status(&self);
fn print_status_into(&self, out: &mut dyn std::io::Write) -> Result<(), std::io::Error>;
fn print_status(&self) {
if let Err(e) = self.print_status_into(&mut std::io::stdout()) {
eprintln!("Unexpected print error: {:?}", e);
}
}
}

View File

@@ -0,0 +1,30 @@
use crate::PrintStatus;
pub struct Reporter<'a> {
reportables: Vec<&'a dyn PrintStatus>,
}
impl<'a> Reporter<'a> {
pub fn new() -> Self {
Self { reportables: Vec::new() }
}
pub fn add_reportable<T: PrintStatus>(mut self, reportable: &'a T) -> Self {
self.reportables.push(reportable);
self
}
pub fn report(&self) {
println!("{}", "=".repeat(16));
for reportable in &self.reportables {
reportable.print_status();
println!("\n{}", "=".repeat(16));
}
}
}
impl<'a> Default for Reporter<'a> {
fn default() -> Self {
Self::new()
}
}

View File

@@ -1,14 +1,19 @@
use crate::{Device, PrintStatus};
use crate::{Device, PrintStatus, Subscriber};
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use std::io::Write;
#[derive(Debug)]
pub struct Room {
devices: HashMap<String, Device>,
subscribers: Vec<Box<dyn Subscriber>>,
}
impl Room {
pub fn new(devices: HashMap<String, Device>) -> Self {
Self { devices }
Self {
devices,
subscribers: Vec::new(),
}
}
pub fn get_device(&self, name: &str) -> Option<&Device> {
@@ -20,20 +25,43 @@ impl Room {
}
pub fn insert_device(&mut self, name: &str, device: Device) -> Option<Device> {
for subscriber in &mut self.subscribers {
subscriber.on_event(&device);
}
self.devices.insert(name.to_string(), device)
}
pub fn remove_device(&mut self, name: &str) -> Option<Device> {
self.devices.remove(name)
}
pub fn subscribe(&mut self, subscriber: impl Subscriber + 'static) {
self.subscribers.push(Box::new(subscriber));
}
}
impl PrintStatus for Room {
fn print_status(&self) {
fn print_status_into(&self, out: &mut dyn Write) -> Result<(), std::io::Error> {
for (name, device) in self.devices.iter() {
print!("{} => ", name);
device.print_status();
out.write_fmt(format_args!("{} => ", name))?;
device.print_status_into(out)?;
}
Ok(())
}
}
impl Debug for Room {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Room")
.field("devices", &self.devices)
.field("subscribers.len()", &self.subscribers.len())
.finish()
}
}
impl Default for Room {
fn default() -> Self {
Room::new(HashMap::new())
}
}

View File

@@ -0,0 +1,14 @@
use crate::Device;
pub trait Subscriber {
fn on_event(&mut self, device: &Device);
}
impl<F> Subscriber for F
where
F: Fn(&Device),
{
fn on_event(&mut self, value: &Device) {
self(value)
}
}